openseadragon 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: aea794949446682eb81b9255301de69b7bd30709
4
- data.tar.gz: cb62202856c8db334095da210127936e5a4eb059
3
+ metadata.gz: 18d43d32894da3240f3369a3b12c81e229a61466
4
+ data.tar.gz: d58c49ef95383a9bb8feea8f3e239daa6451a7b4
5
5
  SHA512:
6
- metadata.gz: dcc726482315230456d2612a380c1ca1b2802fbe153084f33a7e0dfe9e1c15026b6cd88afbaa9fc976889f2379636b7c07fc6f01252dd5899855a698a65b1d09
7
- data.tar.gz: a9b241b6d08fe797bafb39a27be43b60e2c3e7bc79acafd603dd26c306ec56c6bdc49b2f77ec142f472387e696492bd4f9c3cc6eb082a575bbdb7a7faddfe700
6
+ metadata.gz: f70211dcbe9ee4f97e2fdcc5ea204fe13a1d16a91e0c891c61e7523874c132c489b4ffe675c83009f8e4ec26203e6199472867b844455808f8e81e3a256e92f6
7
+ data.tar.gz: 8336ab2fa8befbcc031f993d62768b1b33e32b54a441bb13c0da321adc2fb36b26ba2c2fb5e937fc2217a13edeeaa2e2587d54a63347610beffcc2c4125a6723
@@ -38,5 +38,5 @@
38
38
  };
39
39
 
40
40
  window.onload = initOpenSeadragon;
41
- document.addEventListener("page:load", initOpenSeadragon);
41
+ document.addEventListener("page:load", initOpenSeadragon, false);
42
42
  })(jQuery);
@@ -1,3 +1,3 @@
1
1
  module Openseadragon
2
- VERSION = "0.0.9"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,6 +1,6 @@
1
- //! OpenSeadragon 1.1.1
2
- //! Built on 2014-05-08
3
- //! Git commit: v1.1.1-0-g9aa0f32
1
+ //! OpenSeadragon 1.2.1
2
+ //! Built on 2015-01-29
3
+ //! Git commit: v1.2.1-0-2f9563d
4
4
  //! http://openseadragon.github.io
5
5
  //! License: http://openseadragon.github.io/license/
6
6
 
@@ -89,7 +89,7 @@
89
89
 
90
90
 
91
91
  /**
92
- * @version OpenSeadragon 1.1.1
92
+ * @version OpenSeadragon 1.2.1
93
93
  *
94
94
  * @file
95
95
  * <h2><strong>OpenSeadragon - Javascript Deep Zooming</strong></h2>
@@ -132,6 +132,10 @@
132
132
  * The element to append the viewer's container element to. If not provided, the 'id' property must be provided.
133
133
  * If both the element and id properties are specified, the viewer is appended to the element provided in the element property.
134
134
  *
135
+ * @property {Number} [tabIndex=0]
136
+ * Tabbing order index to assign to the viewer element. Positive values are selected in increasing order. When tabIndex is 0
137
+ * source order is used. A negative value omits the viewer from the tabbing order.
138
+ *
135
139
  * @property {Array|String|Function|Object[]|Array[]|String[]|Function[]} [tileSources=null]
136
140
  * As an Array, the tileSource can hold either Objects or mixed
137
141
  * types of Arrays of Objects, Strings, or Functions. When a value is a String,
@@ -227,6 +231,10 @@
227
231
  *
228
232
  * @property {Number} [maxZoomLevel=null]
229
233
  *
234
+ * @property {Boolean} [homeFillsViewer=false]
235
+ * Make the 'home' button fill the viewer and clip the image, instead
236
+ * of fitting the image to the viewer and letterboxing.
237
+ *
230
238
  * @property {Boolean} [panHorizontal=true]
231
239
  * Allow horizontal pan.
232
240
  *
@@ -304,6 +312,7 @@
304
312
  * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture
305
313
  * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
306
314
  * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
315
+ * @property {Boolean} [gestureSettingsMouse.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
307
316
  *
308
317
  * @property {OpenSeadragon.GestureSettings} [gestureSettingsTouch]
309
318
  * Settings for gestures generated by a touch pointer device. (See {@link OpenSeadragon.GestureSettings})
@@ -315,6 +324,7 @@
315
324
  * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture
316
325
  * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
317
326
  * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
327
+ * @property {Boolean} [gestureSettingsTouch.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
318
328
  *
319
329
  * @property {OpenSeadragon.GestureSettings} [gestureSettingsPen]
320
330
  * Settings for gestures generated by a pen pointer device. (See {@link OpenSeadragon.GestureSettings})
@@ -326,6 +336,7 @@
326
336
  * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture
327
337
  * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
328
338
  * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
339
+ * @property {Boolean} [gestureSettingsPen.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
329
340
  *
330
341
  * @property {OpenSeadragon.GestureSettings} [gestureSettingsUnknown]
331
342
  * Settings for gestures generated by unknown pointer devices. (See {@link OpenSeadragon.GestureSettings})
@@ -337,6 +348,7 @@
337
348
  * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture
338
349
  * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
339
350
  * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
351
+ * @property {Boolean} [gestureSettingsUnknown.pinchRotate=false] - If pinchRotate is true, the user will have the ability to rotate the image using their fingers.
340
352
  *
341
353
  * @property {Number} [zoomPerClick=2.0]
342
354
  * The "zoom distance" per mouse click or touch tap. <em><strong>Note:</strong> Setting this to 1.0 effectively disables the click-to-zoom feature (also see gestureSettings[Mouse|Touch|Pen].clickToZoom/dblClickToZoom).</em>
@@ -384,6 +396,9 @@
384
396
  * Set to false to prevent polling for navigator size changes. Useful for providing custom resize behavior.
385
397
  * Setting to false can also improve performance when the navigator is configured to a fixed size.
386
398
  *
399
+ * @property {Boolean} [navigatorRotate=true]
400
+ * If true, the navigator will be rotated together with the viewer.
401
+ *
387
402
  * @property {Number} [controlsFadeDelay=2000]
388
403
  * The number of milliseconds to wait once the user has stopped interacting
389
404
  * with the interface before begining to fade the controls. Assumes
@@ -510,10 +525,18 @@
510
525
  *
511
526
  * @property {Boolean} [preserveViewport=false]
512
527
  * If the viewer has been configured with a sequence of tile sources, then
513
- * normally navigating to through each image resets the viewport to 'home'
528
+ * normally navigating through each image resets the viewport to 'home'
514
529
  * position. If preserveViewport is set to true, then the viewport position
515
530
  * is preserved when navigating between images in the sequence.
516
531
  *
532
+ * @property {Boolean} [preserveOverlays=false]
533
+ * If the viewer has been configured with a sequence of tile sources, then
534
+ * normally navigating through each image resets the overlays.
535
+ * If preserveOverlays is set to true, then the overlays
536
+ * are preserved when navigating between images in the sequence.
537
+ * Note: setting preserveOverlays overrides any overlays specified in the
538
+ * "overlays" property.
539
+ *
517
540
  * @property {Boolean} [showReferenceStrip=false]
518
541
  * If the viewer has been configured with a sequence of tile sources, then
519
542
  * display a scrolling strip of image thumbnails for navigating through the images.
@@ -662,14 +685,12 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
662
685
  * @property {Number} revision - The revision number.
663
686
  * @since 1.0.0
664
687
  */
665
- /* jshint ignore:start */
666
688
  $.version = {
667
- versionStr: '1.1.1',
668
- major: 1,
669
- minor: 1,
670
- revision: 1
689
+ versionStr: '1.2.1',
690
+ major: parseInt('1', 10),
691
+ minor: parseInt('2', 10),
692
+ revision: parseInt('1', 10)
671
693
  };
672
- /* jshint ignore:end */
673
694
 
674
695
 
675
696
  /**
@@ -913,6 +934,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
913
934
  defaultZoomLevel: 0,
914
935
  minZoomLevel: null,
915
936
  maxZoomLevel: null,
937
+ homeFillsViewer: false,
916
938
 
917
939
  //UI RESPONSIVENESS AND FEEL
918
940
  clickTimeThreshold: 300,
@@ -921,10 +943,10 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
921
943
  dblClickDistThreshold: 20,
922
944
  springStiffness: 6.5,
923
945
  animationTime: 1.2,
924
- gestureSettingsMouse: { scrollToZoom: true, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25 },
925
- gestureSettingsTouch: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25 },
926
- gestureSettingsPen: { scrollToZoom: false, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25 },
927
- gestureSettingsUnknown: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25 },
946
+ gestureSettingsMouse: { scrollToZoom: true, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
947
+ gestureSettingsTouch: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
948
+ gestureSettingsPen: { scrollToZoom: false, clickToZoom: true, dblClickToZoom: false, pinchToZoom: false, flickEnabled: false, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
949
+ gestureSettingsUnknown: { scrollToZoom: false, clickToZoom: false, dblClickToZoom: true, pinchToZoom: true, flickEnabled: true, flickMinSpeed: 120, flickMomentum: 0.25, pinchRotate: false },
928
950
  zoomPerClick: 2,
929
951
  zoomPerScroll: 1.2,
930
952
  zoomPerSecond: 1.0,
@@ -941,6 +963,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
941
963
  showSequenceControl: true, //SEQUENCE
942
964
  sequenceControlAnchor: null, //SEQUENCE
943
965
  preserveViewport: false, //SEQUENCE
966
+ preserveOverlays: false, //SEQUENCE
944
967
  navPrevNextWrap: false, //SEQUENCE
945
968
  showNavigationControl: true, //ZOOM/HOME/FULL/ROTATION
946
969
  navigationControlAnchor: null, //ZOOM/HOME/FULL/ROTATION
@@ -963,6 +986,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
963
986
  navigatorHeight: null,
964
987
  navigatorWidth: null,
965
988
  navigatorAutoResize: true,
989
+ navigatorRotate: true,
966
990
 
967
991
  // INITIAL ROTATION
968
992
  degrees: 0,
@@ -1593,6 +1617,21 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
1593
1617
  },
1594
1618
 
1595
1619
 
1620
+ /**
1621
+ * Sets the specified element's touch-action style attribute to 'none'.
1622
+ * @function
1623
+ * @param {Element|String} element
1624
+ */
1625
+ setElementTouchActionNone: function( element ) {
1626
+ element = $.getElement( element );
1627
+ if ( typeof element.style.touchAction !== 'undefined' ) {
1628
+ element.style.touchAction = 'none';
1629
+ } else if ( typeof element.style.msTouchAction !== 'undefined' ) {
1630
+ element.style.msTouchAction = 'none';
1631
+ }
1632
+ },
1633
+
1634
+
1596
1635
  /**
1597
1636
  * Add the specified CSS class to the element if not present.
1598
1637
  * @function
@@ -2167,7 +2206,7 @@ window.OpenSeadragon = window.OpenSeadragon || function( options ){
2167
2206
  )
2168
2207
  );
2169
2208
  } else {
2170
- regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,}) ");
2209
+ regex = new RegExp( "Trident/.*rv:([0-9]{1,}[.0-9]{0,})");
2171
2210
  if ( regex.exec( ua ) !== null ) {
2172
2211
  $.Browser.vendor = $.BROWSERS.IE;
2173
2212
  $.Browser.version = parseFloat( RegExp.$1 );
@@ -2874,7 +2913,10 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2874
2913
 
2875
2914
  (function ( $ ) {
2876
2915
 
2877
- // dictionary from hash to private properties
2916
+ // All MouseTracker instances
2917
+ var MOUSETRACKERS = [];
2918
+
2919
+ // dictionary from hash to private properties
2878
2920
  var THIS = {};
2879
2921
 
2880
2922
 
@@ -2891,6 +2933,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2891
2933
  * @param {Element|String} options.element
2892
2934
  * A reference to an element or an element id for which the pointer/key
2893
2935
  * events will be monitored.
2936
+ * @param {Boolean} [options.startDisabled=false]
2937
+ * If true, event tracking on the element will not start until
2938
+ * {@link OpenSeadragon.MouseTracker.setTracking|setTracking} is called.
2894
2939
  * @param {Number} options.clickTimeThreshold
2895
2940
  * The number of milliseconds within which a pointer down-up event combination
2896
2941
  * will be treated as a click gesture.
@@ -2912,8 +2957,12 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2912
2957
  * An optional handler for pointer exit.
2913
2958
  * @param {OpenSeadragon.EventHandler} [options.pressHandler=null]
2914
2959
  * An optional handler for pointer press.
2960
+ * @param {OpenSeadragon.EventHandler} [options.nonPrimaryPressHandler=null]
2961
+ * An optional handler for pointer non-primary button press.
2915
2962
  * @param {OpenSeadragon.EventHandler} [options.releaseHandler=null]
2916
2963
  * An optional handler for pointer release.
2964
+ * @param {OpenSeadragon.EventHandler} [options.nonPrimaryReleaseHandler=null]
2965
+ * An optional handler for pointer non-primary button release.
2917
2966
  * @param {OpenSeadragon.EventHandler} [options.moveHandler=null]
2918
2967
  * An optional handler for pointer move.
2919
2968
  * @param {OpenSeadragon.EventHandler} [options.scrollHandler=null]
@@ -2928,6 +2977,10 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2928
2977
  * An optional handler for after a drag gesture.
2929
2978
  * @param {OpenSeadragon.EventHandler} [options.pinchHandler=null]
2930
2979
  * An optional handler for the pinch gesture.
2980
+ * @param {OpenSeadragon.EventHandler} [options.keyDownHandler=null]
2981
+ * An optional handler for keydown.
2982
+ * @param {OpenSeadragon.EventHandler} [options.keyUpHandler=null]
2983
+ * An optional handler for keyup.
2931
2984
  * @param {OpenSeadragon.EventHandler} [options.keyHandler=null]
2932
2985
  * An optional handler for keypress.
2933
2986
  * @param {OpenSeadragon.EventHandler} [options.focusHandler=null]
@@ -2939,6 +2992,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2939
2992
  */
2940
2993
  $.MouseTracker = function ( options ) {
2941
2994
 
2995
+ MOUSETRACKERS.push( this );
2996
+
2942
2997
  var args = arguments;
2943
2998
 
2944
2999
  if ( !$.isPlainObject( options ) ) {
@@ -2962,46 +3017,50 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
2962
3017
  * @member {Number} clickTimeThreshold
2963
3018
  * @memberof OpenSeadragon.MouseTracker#
2964
3019
  */
2965
- this.clickTimeThreshold = options.clickTimeThreshold;
3020
+ this.clickTimeThreshold = options.clickTimeThreshold || $.DEFAULT_SETTINGS.clickTimeThreshold;
2966
3021
  /**
2967
3022
  * The maximum distance allowed between a pointer down event and a pointer up event
2968
3023
  * to be treated as a click gesture.
2969
3024
  * @member {Number} clickDistThreshold
2970
3025
  * @memberof OpenSeadragon.MouseTracker#
2971
3026
  */
2972
- this.clickDistThreshold = options.clickDistThreshold;
3027
+ this.clickDistThreshold = options.clickDistThreshold || $.DEFAULT_SETTINGS.clickDistThreshold;
2973
3028
  /**
2974
3029
  * The number of milliseconds within which two pointer down-up event combinations
2975
3030
  * will be treated as a double-click gesture.
2976
3031
  * @member {Number} dblClickTimeThreshold
2977
3032
  * @memberof OpenSeadragon.MouseTracker#
2978
3033
  */
2979
- this.dblClickTimeThreshold = options.dblClickTimeThreshold;
3034
+ this.dblClickTimeThreshold = options.dblClickTimeThreshold || $.DEFAULT_SETTINGS.dblClickTimeThreshold;
2980
3035
  /**
2981
3036
  * The maximum distance allowed between two pointer click events
2982
3037
  * to be treated as a click gesture.
2983
3038
  * @member {Number} clickDistThreshold
2984
3039
  * @memberof OpenSeadragon.MouseTracker#
2985
3040
  */
2986
- this.dblClickDistThreshold = options.dblClickDistThreshold;
2987
- this.userData = options.userData || null;
2988
- this.stopDelay = options.stopDelay || 50;
2989
-
2990
- this.enterHandler = options.enterHandler || null;
2991
- this.exitHandler = options.exitHandler || null;
2992
- this.pressHandler = options.pressHandler || null;
2993
- this.releaseHandler = options.releaseHandler || null;
2994
- this.moveHandler = options.moveHandler || null;
2995
- this.scrollHandler = options.scrollHandler || null;
2996
- this.clickHandler = options.clickHandler || null;
2997
- this.dblClickHandler = options.dblClickHandler || null;
2998
- this.dragHandler = options.dragHandler || null;
2999
- this.dragEndHandler = options.dragEndHandler || null;
3000
- this.pinchHandler = options.pinchHandler || null;
3001
- this.stopHandler = options.stopHandler || null;
3002
- this.keyHandler = options.keyHandler || null;
3003
- this.focusHandler = options.focusHandler || null;
3004
- this.blurHandler = options.blurHandler || null;
3041
+ this.dblClickDistThreshold = options.dblClickDistThreshold || $.DEFAULT_SETTINGS.dblClickDistThreshold;
3042
+ this.userData = options.userData || null;
3043
+ this.stopDelay = options.stopDelay || 50;
3044
+
3045
+ this.enterHandler = options.enterHandler || null;
3046
+ this.exitHandler = options.exitHandler || null;
3047
+ this.pressHandler = options.pressHandler || null;
3048
+ this.nonPrimaryPressHandler = options.nonPrimaryPressHandler || null;
3049
+ this.releaseHandler = options.releaseHandler || null;
3050
+ this.nonPrimaryReleaseHandler = options.nonPrimaryReleaseHandler || null;
3051
+ this.moveHandler = options.moveHandler || null;
3052
+ this.scrollHandler = options.scrollHandler || null;
3053
+ this.clickHandler = options.clickHandler || null;
3054
+ this.dblClickHandler = options.dblClickHandler || null;
3055
+ this.dragHandler = options.dragHandler || null;
3056
+ this.dragEndHandler = options.dragEndHandler || null;
3057
+ this.pinchHandler = options.pinchHandler || null;
3058
+ this.stopHandler = options.stopHandler || null;
3059
+ this.keyDownHandler = options.keyDownHandler || null;
3060
+ this.keyUpHandler = options.keyUpHandler || null;
3061
+ this.keyHandler = options.keyHandler || null;
3062
+ this.focusHandler = options.focusHandler || null;
3063
+ this.blurHandler = options.blurHandler || null;
3005
3064
 
3006
3065
  //Store private properties in a scope sealed hash map
3007
3066
  var _this = this;
@@ -3010,12 +3069,12 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3010
3069
  * @private
3011
3070
  * @property {Boolean} tracking
3012
3071
  * Are we currently tracking pointer events for this element.
3013
- * @property {Boolean} capturing
3014
- * Are we curruently capturing mouse events (legacy mouse events only).
3015
3072
  */
3016
3073
  THIS[ this.hash ] = {
3017
3074
  click: function ( event ) { onClick( _this, event ); },
3018
3075
  dblclick: function ( event ) { onDblClick( _this, event ); },
3076
+ keydown: function ( event ) { onKeyDown( _this, event ); },
3077
+ keyup: function ( event ) { onKeyUp( _this, event ); },
3019
3078
  keypress: function ( event ) { onKeyPress( _this, event ); },
3020
3079
  focus: function ( event ) { onFocus( _this, event ); },
3021
3080
  blur: function ( event ) { onBlur( _this, event ); },
@@ -3025,30 +3084,30 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3025
3084
  DOMMouseScroll: function ( event ) { onMouseWheel( _this, event ); },
3026
3085
  MozMousePixelScroll: function ( event ) { onMouseWheel( _this, event ); },
3027
3086
 
3087
+ mouseenter: function ( event ) { onMouseEnter( _this, event ); }, // Used on IE8 only
3088
+ mouseleave: function ( event ) { onMouseLeave( _this, event ); }, // Used on IE8 only
3028
3089
  mouseover: function ( event ) { onMouseOver( _this, event ); },
3029
3090
  mouseout: function ( event ) { onMouseOut( _this, event ); },
3030
- mouseenter: function ( event ) { onMouseEnter( _this, event ); },
3031
- mouseleave: function ( event ) { onMouseLeave( _this, event ); },
3032
3091
  mousedown: function ( event ) { onMouseDown( _this, event ); },
3033
3092
  mouseup: function ( event ) { onMouseUp( _this, event ); },
3034
3093
  mouseupcaptured: function ( event ) { onMouseUpCaptured( _this, event ); },
3035
3094
  mousemove: function ( event ) { onMouseMove( _this, event ); },
3036
3095
  mousemovecaptured: function ( event ) { onMouseMoveCaptured( _this, event ); },
3037
3096
 
3038
- touchenter: function ( event ) { onTouchEnter( _this, event ); },
3039
- touchleave: function ( event ) { onTouchLeave( _this, event ); },
3040
3097
  touchstart: function ( event ) { onTouchStart( _this, event ); },
3041
3098
  touchend: function ( event ) { onTouchEnd( _this, event ); },
3099
+ touchendcaptured: function ( event ) { onTouchEndCaptured( _this, event ); },
3042
3100
  touchmove: function ( event ) { onTouchMove( _this, event ); },
3101
+ touchmovecaptured: function ( event ) { onTouchMoveCaptured( _this, event ); },
3043
3102
  touchcancel: function ( event ) { onTouchCancel( _this, event ); },
3044
3103
 
3045
3104
  gesturestart: function ( event ) { onGestureStart( _this, event ); },
3046
3105
  gesturechange: function ( event ) { onGestureChange( _this, event ); },
3047
3106
 
3048
- pointerenter: function ( event ) { onPointerEnter( _this, event ); },
3049
- MSPointerEnter: function ( event ) { onPointerEnter( _this, event ); },
3050
- pointerleave: function ( event ) { onPointerLeave( _this, event ); },
3051
- MSPointerLeave: function ( event ) { onPointerLeave( _this, event ); },
3107
+ pointerover: function ( event ) { onPointerOver( _this, event ); },
3108
+ MSPointerOver: function ( event ) { onPointerOver( _this, event ); },
3109
+ pointerout: function ( event ) { onPointerOut( _this, event ); },
3110
+ MSPointerOut: function ( event ) { onPointerOut( _this, event ); },
3052
3111
  pointerdown: function ( event ) { onPointerDown( _this, event ); },
3053
3112
  MSPointerDown: function ( event ) { onPointerDown( _this, event ); },
3054
3113
  pointerup: function ( event ) { onPointerUp( _this, event ); },
@@ -3057,6 +3116,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3057
3116
  MSPointerMove: function ( event ) { onPointerMove( _this, event ); },
3058
3117
  pointercancel: function ( event ) { onPointerCancel( _this, event ); },
3059
3118
  MSPointerCancel: function ( event ) { onPointerCancel( _this, event ); },
3119
+ pointerupcaptured: function ( event ) { onPointerUpCaptured( _this, event ); },
3120
+ pointermovecaptured: function ( event ) { onPointerMoveCaptured( _this, event ); },
3060
3121
 
3061
3122
  tracking: false,
3062
3123
 
@@ -3066,9 +3127,6 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3066
3127
  // of the element (for hover-capable devices) and/or have contact or a button press initiated in the element.
3067
3128
  activePointersLists: [],
3068
3129
 
3069
- // Legacy mouse event tracking
3070
- capturing: false,
3071
-
3072
3130
  // Tracking for double-click gesture
3073
3131
  lastClickPos: null,
3074
3132
  dblClickTimeOut: null,
@@ -3081,6 +3139,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3081
3139
  currentPinchCenter: null
3082
3140
  };
3083
3141
 
3142
+ if ( !options.startDisabled ) {
3143
+ this.setTracking( true );
3144
+ }
3084
3145
  };
3085
3146
 
3086
3147
  $.MouseTracker.prototype = /** @lends OpenSeadragon.MouseTracker.prototype */{
@@ -3090,8 +3151,20 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3090
3151
  * @function
3091
3152
  */
3092
3153
  destroy: function () {
3154
+ var i;
3155
+
3093
3156
  stopTracking( this );
3094
3157
  this.element = null;
3158
+
3159
+ for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
3160
+ if ( MOUSETRACKERS[ i ] === this ) {
3161
+ MOUSETRACKERS.splice( i, 1 );
3162
+ break;
3163
+ }
3164
+ }
3165
+
3166
+ THIS[ this.hash ] = null;
3167
+ delete THIS[ this.hash ];
3095
3168
  },
3096
3169
 
3097
3170
  /**
@@ -3144,6 +3217,24 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3144
3217
  return list;
3145
3218
  },
3146
3219
 
3220
+ /**
3221
+ * Returns the total number of pointers currently active on the tracked element.
3222
+ * @function
3223
+ * @returns {Number}
3224
+ */
3225
+ getActivePointerCount: function () {
3226
+ var delegate = THIS[ this.hash ],
3227
+ i,
3228
+ len = delegate.activePointersLists.length,
3229
+ count = 0;
3230
+
3231
+ for ( i = 0; i < len; i++ ) {
3232
+ count += delegate.activePointersLists[ i ].getLength();
3233
+ }
3234
+
3235
+ return count;
3236
+ },
3237
+
3147
3238
  /**
3148
3239
  * Implement or assign implementation to these handlers during or after
3149
3240
  * calling the constructor.
@@ -3158,6 +3249,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3158
3249
  * @param {Number} event.buttons
3159
3250
  * Current buttons pressed.
3160
3251
  * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3252
+ * @param {Number} event.pointers
3253
+ * Number of pointers (all types) active in the tracked element.
3161
3254
  * @param {Boolean} event.insideElementPressed
3162
3255
  * True if the left mouse button is currently being pressed and was
3163
3256
  * initiated inside the tracked element, otherwise false.
@@ -3188,6 +3281,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3188
3281
  * @param {Number} event.buttons
3189
3282
  * Current buttons pressed.
3190
3283
  * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3284
+ * @param {Number} event.pointers
3285
+ * Number of pointers (all types) active in the tracked element.
3191
3286
  * @param {Boolean} event.insideElementPressed
3192
3287
  * True if the left mouse button is currently being pressed and was
3193
3288
  * initiated inside the tracked element, otherwise false.
@@ -3229,6 +3324,34 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3229
3324
  */
3230
3325
  pressHandler: function () { },
3231
3326
 
3327
+ /**
3328
+ * Implement or assign implementation to these handlers during or after
3329
+ * calling the constructor.
3330
+ * @function
3331
+ * @param {Object} event
3332
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3333
+ * A reference to the tracker instance.
3334
+ * @param {String} event.pointerType
3335
+ * "mouse", "touch", "pen", etc.
3336
+ * @param {OpenSeadragon.Point} event.position
3337
+ * The position of the event relative to the tracked element.
3338
+ * @param {Number} event.button
3339
+ * Button which caused the event.
3340
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
3341
+ * @param {Number} event.buttons
3342
+ * Current buttons pressed.
3343
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3344
+ * @param {Boolean} event.isTouchEvent
3345
+ * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3346
+ * @param {Object} event.originalEvent
3347
+ * The original event object.
3348
+ * @param {Boolean} event.preventDefaultAction
3349
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3350
+ * @param {Object} event.userData
3351
+ * Arbitrary user-defined object.
3352
+ */
3353
+ nonPrimaryPressHandler: function () { },
3354
+
3232
3355
  /**
3233
3356
  * Implement or assign implementation to these handlers during or after
3234
3357
  * calling the constructor.
@@ -3259,6 +3382,34 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3259
3382
  */
3260
3383
  releaseHandler: function () { },
3261
3384
 
3385
+ /**
3386
+ * Implement or assign implementation to these handlers during or after
3387
+ * calling the constructor.
3388
+ * @function
3389
+ * @param {Object} event
3390
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3391
+ * A reference to the tracker instance.
3392
+ * @param {String} event.pointerType
3393
+ * "mouse", "touch", "pen", etc.
3394
+ * @param {OpenSeadragon.Point} event.position
3395
+ * The position of the event relative to the tracked element.
3396
+ * @param {Number} event.button
3397
+ * Button which caused the event.
3398
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
3399
+ * @param {Number} event.buttons
3400
+ * Current buttons pressed.
3401
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
3402
+ * @param {Boolean} event.isTouchEvent
3403
+ * True if the original event is a touch event, otherwise false. <span style="color:red;">Deprecated. Use pointerType and/or originalEvent instead.</span>
3404
+ * @param {Object} event.originalEvent
3405
+ * The original event object.
3406
+ * @param {Boolean} event.preventDefaultAction
3407
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3408
+ * @param {Object} event.userData
3409
+ * Arbitrary user-defined object.
3410
+ */
3411
+ nonPrimaryReleaseHandler: function () { },
3412
+
3262
3413
  /**
3263
3414
  * Implement or assign implementation to these handlers during or after
3264
3415
  * calling the constructor.
@@ -3485,8 +3636,66 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3485
3636
  * A reference to the tracker instance.
3486
3637
  * @param {Number} event.keyCode
3487
3638
  * The key code that was pressed.
3639
+ * @param {Boolean} event.ctrl
3640
+ * True if the ctrl key was pressed during this event.
3641
+ * @param {Boolean} event.shift
3642
+ * True if the shift key was pressed during this event.
3643
+ * @param {Boolean} event.alt
3644
+ * True if the alt key was pressed during this event.
3645
+ * @param {Boolean} event.meta
3646
+ * True if the meta key was pressed during this event.
3647
+ * @param {Object} event.originalEvent
3648
+ * The original event object.
3649
+ * @param {Boolean} event.preventDefaultAction
3650
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3651
+ * @param {Object} event.userData
3652
+ * Arbitrary user-defined object.
3653
+ */
3654
+ keyDownHandler: function () { },
3655
+
3656
+ /**
3657
+ * Implement or assign implementation to these handlers during or after
3658
+ * calling the constructor.
3659
+ * @function
3660
+ * @param {Object} event
3661
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3662
+ * A reference to the tracker instance.
3663
+ * @param {Number} event.keyCode
3664
+ * The key code that was pressed.
3665
+ * @param {Boolean} event.ctrl
3666
+ * True if the ctrl key was pressed during this event.
3667
+ * @param {Boolean} event.shift
3668
+ * True if the shift key was pressed during this event.
3669
+ * @param {Boolean} event.alt
3670
+ * True if the alt key was pressed during this event.
3671
+ * @param {Boolean} event.meta
3672
+ * True if the meta key was pressed during this event.
3673
+ * @param {Object} event.originalEvent
3674
+ * The original event object.
3675
+ * @param {Boolean} event.preventDefaultAction
3676
+ * Set to true to prevent the tracker subscriber from performing its default action (subscriber implementation dependent). Default: false.
3677
+ * @param {Object} event.userData
3678
+ * Arbitrary user-defined object.
3679
+ */
3680
+ keyUpHandler: function () { },
3681
+
3682
+ /**
3683
+ * Implement or assign implementation to these handlers during or after
3684
+ * calling the constructor.
3685
+ * @function
3686
+ * @param {Object} event
3687
+ * @param {OpenSeadragon.MouseTracker} event.eventSource
3688
+ * A reference to the tracker instance.
3689
+ * @param {Number} event.keyCode
3690
+ * The key code that was pressed.
3691
+ * @param {Boolean} event.ctrl
3692
+ * True if the ctrl key was pressed during this event.
3488
3693
  * @param {Boolean} event.shift
3489
3694
  * True if the shift key was pressed during this event.
3695
+ * @param {Boolean} event.alt
3696
+ * True if the alt key was pressed during this event.
3697
+ * @param {Boolean} event.meta
3698
+ * True if the meta key was pressed during this event.
3490
3699
  * @param {Object} event.originalEvent
3491
3700
  * The original event object.
3492
3701
  * @param {Boolean} event.preventDefaultAction
@@ -3625,6 +3834,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3625
3834
  // Pointer event model and feature detection
3626
3835
  ///////////////////////////////////////////////////////////////////////////////
3627
3836
 
3837
+ $.MouseTracker.captureElement = document;
3838
+
3628
3839
  /**
3629
3840
  * Detect available mouse wheel event name.
3630
3841
  */
@@ -3644,54 +3855,56 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3644
3855
  /**
3645
3856
  * Detect browser pointer device event model(s) and build appropriate list of events to subscribe to.
3646
3857
  */
3647
- $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
3858
+ $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keydown", "keyup", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];
3648
3859
 
3649
3860
  if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
3650
3861
  // Older Firefox
3651
3862
  $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
3652
3863
  }
3653
3864
 
3654
- if ( window.PointerEvent ) {
3865
+ // Note: window.navigator.pointerEnable is deprecated on IE 11 and not part of W3C spec.
3866
+ if ( window.PointerEvent && ( window.navigator.pointerEnabled || $.Browser.vendor !== $.BROWSERS.IE ) ) {
3655
3867
  // IE11 and other W3C Pointer Event implementations (see http://www.w3.org/TR/pointerevents)
3656
- $.MouseTracker.subscribeEvents.push( "pointerenter", "pointerleave", "pointerdown", "pointerup", "pointermove", "pointercancel" );
3868
+ $.MouseTracker.havePointerEvents = true;
3869
+ $.MouseTracker.subscribeEvents.push( "pointerover", "pointerout", "pointerdown", "pointerup", "pointermove", "pointercancel" );
3657
3870
  $.MouseTracker.unprefixedPointerEvents = true;
3658
3871
  if( navigator.maxTouchPoints ) {
3659
3872
  $.MouseTracker.maxTouchPoints = navigator.maxTouchPoints;
3660
3873
  } else {
3661
3874
  $.MouseTracker.maxTouchPoints = 0;
3662
3875
  }
3663
- $.MouseTracker.haveTouchEnter = true;
3664
- $.MouseTracker.haveMouseEnter = true;
3665
- } else if ( window.MSPointerEvent ) {
3876
+ $.MouseTracker.haveMouseEnter = false;
3877
+ } else if ( window.MSPointerEvent && window.navigator.msPointerEnabled ) {
3666
3878
  // IE10
3667
- $.MouseTracker.subscribeEvents.push( "MSPointerEnter", "MSPointerLeave", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" );
3879
+ $.MouseTracker.havePointerEvents = true;
3880
+ $.MouseTracker.subscribeEvents.push( "MSPointerOver", "MSPointerOut", "MSPointerDown", "MSPointerUp", "MSPointerMove", "MSPointerCancel" );
3668
3881
  $.MouseTracker.unprefixedPointerEvents = false;
3669
3882
  if( navigator.msMaxTouchPoints ) {
3670
3883
  $.MouseTracker.maxTouchPoints = navigator.msMaxTouchPoints;
3671
3884
  } else {
3672
3885
  $.MouseTracker.maxTouchPoints = 0;
3673
3886
  }
3674
- $.MouseTracker.haveTouchEnter = true;
3675
- $.MouseTracker.haveMouseEnter = true;
3887
+ $.MouseTracker.haveMouseEnter = false;
3676
3888
  } else {
3677
3889
  // Legacy W3C mouse events
3678
- // TODO: Favor mouseenter/mouseleave over mouseover/mouseout when Webkit browser support is better
3679
- $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" );
3680
- $.MouseTracker.haveMouseEnter = false;
3890
+ $.MouseTracker.havePointerEvents = false;
3891
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
3892
+ $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" );
3893
+ $.MouseTracker.haveMouseEnter = true;
3894
+ } else {
3895
+ $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" );
3896
+ $.MouseTracker.haveMouseEnter = false;
3897
+ }
3898
+ $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" );
3681
3899
  if ( 'ontouchstart' in window ) {
3682
- // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505)
3900
+ // iOS, Android, and other W3c Touch Event implementations
3901
+ // (see http://www.w3.org/TR/touch-events/)
3902
+ // (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
3903
+ // (see https://developer.apple.com/library/safari/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
3683
3904
  $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
3684
- if ( 'ontouchenter' in window ) {
3685
- $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" );
3686
- $.MouseTracker.haveTouchEnter = true;
3687
- } else {
3688
- $.MouseTracker.haveTouchEnter = false;
3689
- }
3690
- } else {
3691
- $.MouseTracker.haveTouchEnter = false;
3692
3905
  }
3693
3906
  if ( 'ongesturestart' in window ) {
3694
- // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html)
3907
+ // iOS (see https://developer.apple.com/library/ios/documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html)
3695
3908
  // Subscribe to these to prevent default gesture handling
3696
3909
  $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
3697
3910
  }
@@ -3776,6 +3989,12 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3776
3989
  * @memberof OpenSeadragon.MouseTracker.GesturePointList#
3777
3990
  */
3778
3991
  this.clicks = 0;
3992
+ /**
3993
+ * Current number of captured pointers for the device.
3994
+ * @member {Number} captureCount
3995
+ * @memberof OpenSeadragon.MouseTracker.GesturePointList#
3996
+ */
3997
+ this.captureCount = 0;
3779
3998
  };
3780
3999
  $.MouseTracker.GesturePointList.prototype = /** @lends OpenSeadragon.MouseTracker.GesturePointList.prototype */{
3781
4000
  /**
@@ -3864,6 +4083,64 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3864
4083
  // Utility functions
3865
4084
  ///////////////////////////////////////////////////////////////////////////////
3866
4085
 
4086
+ /**
4087
+ * Removes all tracked pointers.
4088
+ * @private
4089
+ * @inner
4090
+ */
4091
+ function clearTrackedPointers( tracker ) {
4092
+ var delegate = THIS[ tracker.hash ],
4093
+ i,
4094
+ pointerListCount = delegate.activePointersLists.length;
4095
+
4096
+ for ( i = 0; i < pointerListCount; i++ ) {
4097
+ if ( delegate.activePointersLists[ i ].captureCount > 0 ) {
4098
+ $.removeEvent(
4099
+ $.MouseTracker.captureElement,
4100
+ 'mousemove',
4101
+ delegate.mousemovecaptured,
4102
+ true
4103
+ );
4104
+ $.removeEvent(
4105
+ $.MouseTracker.captureElement,
4106
+ 'mouseup',
4107
+ delegate.mouseupcaptured,
4108
+ true
4109
+ );
4110
+ $.removeEvent(
4111
+ $.MouseTracker.captureElement,
4112
+ $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
4113
+ delegate.pointermovecaptured,
4114
+ true
4115
+ );
4116
+ $.removeEvent(
4117
+ $.MouseTracker.captureElement,
4118
+ $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
4119
+ delegate.pointerupcaptured,
4120
+ true
4121
+ );
4122
+ $.removeEvent(
4123
+ $.MouseTracker.captureElement,
4124
+ 'touchmove',
4125
+ delegate.touchmovecaptured,
4126
+ true
4127
+ );
4128
+ $.removeEvent(
4129
+ $.MouseTracker.captureElement,
4130
+ 'touchend',
4131
+ delegate.touchendcaptured,
4132
+ true
4133
+ );
4134
+
4135
+ delegate.activePointersLists[ i ].captureCount = 0;
4136
+ }
4137
+ }
4138
+
4139
+ for ( i = 0; i < pointerListCount; i++ ) {
4140
+ delegate.activePointersLists.pop();
4141
+ }
4142
+ }
4143
+
3867
4144
  /**
3868
4145
  * Starts tracking pointer events on the tracked element.
3869
4146
  * @private
@@ -3884,6 +4161,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3884
4161
  false
3885
4162
  );
3886
4163
  }
4164
+
4165
+ clearTrackedPointers( tracker );
4166
+
3887
4167
  delegate.tracking = true;
3888
4168
  }
3889
4169
  }
@@ -3909,73 +4189,111 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
3909
4189
  );
3910
4190
  }
3911
4191
 
3912
- releaseMouse( tracker );
4192
+ clearTrackedPointers( tracker );
4193
+
3913
4194
  delegate.tracking = false;
3914
4195
  }
3915
4196
  }
3916
4197
 
3917
4198
  /**
3918
- * Begin capturing mouse events to the tracked element (legacy mouse events only).
3919
4199
  * @private
3920
4200
  * @inner
3921
4201
  */
3922
- function captureMouse( tracker ) {
4202
+ function getCaptureEventParams( tracker, pointerType ) {
3923
4203
  var delegate = THIS[ tracker.hash ];
3924
4204
 
3925
- if ( !delegate.capturing ) {
3926
- if ( $.MouseTracker.supportsMouseCapture ) {
3927
- // IE<10, Firefox, other browsers with setCapture()/releaseCapture()
4205
+ if ( pointerType === 'pointerevent' ) {
4206
+ return {
4207
+ upName: $.MouseTracker.unprefixedPointerEvents ? 'pointerup' : 'MSPointerUp',
4208
+ upHandler: delegate.pointerupcaptured,
4209
+ moveName: $.MouseTracker.unprefixedPointerEvents ? 'pointermove' : 'MSPointerMove',
4210
+ moveHandler: delegate.pointermovecaptured
4211
+ };
4212
+ } else if ( pointerType === 'mouse' ) {
4213
+ return {
4214
+ upName: 'mouseup',
4215
+ upHandler: delegate.mouseupcaptured,
4216
+ moveName: 'mousemove',
4217
+ moveHandler: delegate.mousemovecaptured
4218
+ };
4219
+ } else if ( pointerType === 'touch' ) {
4220
+ return {
4221
+ upName: 'touchend',
4222
+ upHandler: delegate.touchendcaptured,
4223
+ moveName: 'touchmove',
4224
+ moveHandler: delegate.touchmovecaptured
4225
+ };
4226
+ } else {
4227
+ throw new Error( "MouseTracker.getCaptureEventParams: Unknown pointer type." );
4228
+ }
4229
+ }
4230
+
4231
+ /**
4232
+ * Begin capturing pointer events to the tracked element.
4233
+ * @private
4234
+ * @inner
4235
+ */
4236
+ function capturePointer( tracker, pointerType ) {
4237
+ var pointsList = tracker.getActivePointersListByType( pointerType ),
4238
+ eventParams;
4239
+
4240
+ pointsList.captureCount++;
4241
+
4242
+ if ( pointsList.captureCount === 1 ) {
4243
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
3928
4244
  tracker.element.setCapture( true );
3929
4245
  } else {
3930
- // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object.
4246
+ eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4247
+ // We emulate mouse capture by hanging listeners on the document object.
3931
4248
  // (Note we listen on the capture phase so the captured handlers will get called first)
3932
4249
  $.addEvent(
3933
- window,
3934
- "mouseup",
3935
- delegate.mouseupcaptured,
4250
+ $.MouseTracker.captureElement,
4251
+ eventParams.upName,
4252
+ eventParams.upHandler,
3936
4253
  true
3937
4254
  );
3938
4255
  $.addEvent(
3939
- window,
3940
- "mousemove",
3941
- delegate.mousemovecaptured,
4256
+ $.MouseTracker.captureElement,
4257
+ eventParams.moveName,
4258
+ eventParams.moveHandler,
3942
4259
  true
3943
4260
  );
3944
4261
  }
3945
- delegate.capturing = true;
3946
4262
  }
3947
4263
  }
3948
4264
 
3949
4265
 
3950
4266
  /**
3951
- * Stop capturing mouse events to the tracked element (legacy mouse events only).
4267
+ * Stop capturing pointer events to the tracked element.
3952
4268
  * @private
3953
4269
  * @inner
3954
4270
  */
3955
- function releaseMouse( tracker ) {
3956
- var delegate = THIS[ tracker.hash ];
4271
+ function releasePointer( tracker, pointerType ) {
4272
+ var pointsList = tracker.getActivePointersListByType( pointerType ),
4273
+ eventParams;
3957
4274
 
3958
- if ( delegate.capturing ) {
3959
- if ( $.MouseTracker.supportsMouseCapture ) {
3960
- // IE<10, Firefox, other browsers with setCapture()/releaseCapture()
4275
+ pointsList.captureCount--;
4276
+
4277
+ if ( pointsList.captureCount === 0 ) {
4278
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
3961
4279
  tracker.element.releaseCapture();
3962
4280
  } else {
3963
- // For browsers without setCapture()/releaseCapture(), we emulate mouse capture by hanging listeners on the window object.
4281
+ eventParams = getCaptureEventParams( tracker, $.MouseTracker.havePointerEvents ? 'pointerevent' : pointerType );
4282
+ // We emulate mouse capture by hanging listeners on the document object.
3964
4283
  // (Note we listen on the capture phase so the captured handlers will get called first)
3965
4284
  $.removeEvent(
3966
- window,
3967
- "mousemove",
3968
- delegate.mousemovecaptured,
4285
+ $.MouseTracker.captureElement,
4286
+ eventParams.moveName,
4287
+ eventParams.moveHandler,
3969
4288
  true
3970
4289
  );
3971
4290
  $.removeEvent(
3972
- window,
3973
- "mouseup",
3974
- delegate.mouseupcaptured,
4291
+ $.MouseTracker.captureElement,
4292
+ eventParams.upName,
4293
+ eventParams.upHandler,
3975
4294
  true
3976
4295
  );
3977
4296
  }
3978
- delegate.capturing = false;
3979
4297
  }
3980
4298
  }
3981
4299
 
@@ -4074,21 +4392,81 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4074
4392
  }
4075
4393
 
4076
4394
 
4395
+ /**
4396
+ * @private
4397
+ * @inner
4398
+ */
4399
+ function onKeyDown( tracker, event ) {
4400
+ //$.console.log( "keydown %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4401
+ var propagate;
4402
+ if ( tracker.keyDownHandler ) {
4403
+ event = $.getEvent( event );
4404
+ propagate = tracker.keyDownHandler(
4405
+ {
4406
+ eventSource: tracker,
4407
+ keyCode: event.keyCode ? event.keyCode : event.charCode,
4408
+ ctrl: event.ctrlKey,
4409
+ shift: event.shiftKey,
4410
+ alt: event.altKey,
4411
+ meta: event.metaKey,
4412
+ originalEvent: event,
4413
+ preventDefaultAction: false,
4414
+ userData: tracker.userData
4415
+ }
4416
+ );
4417
+ if ( !propagate ) {
4418
+ $.cancelEvent( event );
4419
+ }
4420
+ }
4421
+ }
4422
+
4423
+
4424
+ /**
4425
+ * @private
4426
+ * @inner
4427
+ */
4428
+ function onKeyUp( tracker, event ) {
4429
+ //$.console.log( "keyup %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4430
+ var propagate;
4431
+ if ( tracker.keyUpHandler ) {
4432
+ event = $.getEvent( event );
4433
+ propagate = tracker.keyUpHandler(
4434
+ {
4435
+ eventSource: tracker,
4436
+ keyCode: event.keyCode ? event.keyCode : event.charCode,
4437
+ ctrl: event.ctrlKey,
4438
+ shift: event.shiftKey,
4439
+ alt: event.altKey,
4440
+ meta: event.metaKey,
4441
+ originalEvent: event,
4442
+ preventDefaultAction: false,
4443
+ userData: tracker.userData
4444
+ }
4445
+ );
4446
+ if ( !propagate ) {
4447
+ $.cancelEvent( event );
4448
+ }
4449
+ }
4450
+ }
4451
+
4452
+
4077
4453
  /**
4078
4454
  * @private
4079
4455
  * @inner
4080
4456
  */
4081
4457
  function onKeyPress( tracker, event ) {
4082
- //console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4458
+ //$.console.log( "keypress %s %s %s %s %s", event.keyCode, event.charCode, event.ctrlKey, event.shiftKey, event.altKey );
4083
4459
  var propagate;
4084
4460
  if ( tracker.keyHandler ) {
4085
4461
  event = $.getEvent( event );
4086
4462
  propagate = tracker.keyHandler(
4087
4463
  {
4088
4464
  eventSource: tracker,
4089
- position: getMouseRelative( event, tracker.element ),
4090
4465
  keyCode: event.keyCode ? event.keyCode : event.charCode,
4466
+ ctrl: event.ctrlKey,
4091
4467
  shift: event.shiftKey,
4468
+ alt: event.altKey,
4469
+ meta: event.metaKey,
4092
4470
  originalEvent: event,
4093
4471
  preventDefaultAction: false,
4094
4472
  userData: tracker.userData
@@ -4250,27 +4628,15 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4250
4628
 
4251
4629
 
4252
4630
  /**
4631
+ * Only used on IE 8
4632
+ *
4253
4633
  * @private
4254
4634
  * @inner
4255
4635
  */
4256
- function onMouseOver( tracker, event ) {
4257
- var gPoint;
4258
-
4636
+ function onMouseEnter( tracker, event ) {
4259
4637
  event = $.getEvent( event );
4260
4638
 
4261
- if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) {
4262
- return;
4263
- }
4264
-
4265
- gPoint = {
4266
- id: $.MouseTracker.mousePointerId,
4267
- type: 'mouse',
4268
- isPrimary: true,
4269
- currentPos: getMouseAbsolute( event ),
4270
- currentTime: $.now()
4271
- };
4272
-
4273
- updatePointersEnter( tracker, event, [ gPoint ] );
4639
+ handleMouseEnter( tracker, event );
4274
4640
  }
4275
4641
 
4276
4642
 
@@ -4278,24 +4644,14 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4278
4644
  * @private
4279
4645
  * @inner
4280
4646
  */
4281
- function onMouseOut( tracker, event ) {
4282
- var gPoint;
4283
-
4647
+ function onMouseOver( tracker, event ) {
4284
4648
  event = $.getEvent( event );
4285
4649
 
4286
- if ( this === event.relatedTarget || isParentChild( this, event.relatedTarget ) ) {
4650
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
4287
4651
  return;
4288
4652
  }
4289
4653
 
4290
- gPoint = {
4291
- id: $.MouseTracker.mousePointerId,
4292
- type: 'mouse',
4293
- isPrimary: true,
4294
- currentPos: getMouseAbsolute( event ),
4295
- currentTime: $.now()
4296
- };
4297
-
4298
- updatePointersExit( tracker, event, [ gPoint ] );
4654
+ handleMouseEnter( tracker, event );
4299
4655
  }
4300
4656
 
4301
4657
 
@@ -4303,12 +4659,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4303
4659
  * @private
4304
4660
  * @inner
4305
4661
  */
4306
- function onMouseEnter( tracker, event ) {
4307
- var gPoint;
4308
-
4309
- event = $.getEvent( event );
4310
-
4311
- gPoint = {
4662
+ function handleMouseEnter( tracker, event ) {
4663
+ var gPoint = {
4312
4664
  id: $.MouseTracker.mousePointerId,
4313
4665
  type: 'mouse',
4314
4666
  isPrimary: true,
@@ -4321,15 +4673,39 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4321
4673
 
4322
4674
 
4323
4675
  /**
4676
+ * Only used on IE 8
4677
+ *
4324
4678
  * @private
4325
4679
  * @inner
4326
4680
  */
4327
4681
  function onMouseLeave( tracker, event ) {
4328
- var gPoint;
4682
+ event = $.getEvent( event );
4683
+
4684
+ handleMouseExit( tracker, event );
4685
+ }
4686
+
4329
4687
 
4688
+ /**
4689
+ * @private
4690
+ * @inner
4691
+ */
4692
+ function onMouseOut( tracker, event ) {
4330
4693
  event = $.getEvent( event );
4331
4694
 
4332
- gPoint = {
4695
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
4696
+ return;
4697
+ }
4698
+
4699
+ handleMouseExit( tracker, event );
4700
+ }
4701
+
4702
+
4703
+ /**
4704
+ * @private
4705
+ * @inner
4706
+ */
4707
+ function handleMouseExit( tracker, event ) {
4708
+ var gPoint = {
4333
4709
  id: $.MouseTracker.mousePointerId,
4334
4710
  type: 'mouse',
4335
4711
  isPrimary: true,
@@ -4341,6 +4717,31 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4341
4717
  }
4342
4718
 
4343
4719
 
4720
+ /**
4721
+ * Returns a W3C DOM level 3 standard button value given an event.button property:
4722
+ * -1 == none, 0 == primary/left, 1 == middle, 2 == secondary/right, 3 == X1/back, 4 == X2/forward, 5 == eraser (pen)
4723
+ * @private
4724
+ * @inner
4725
+ */
4726
+ function getStandardizedButton( button ) {
4727
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
4728
+ // On IE 8, 0 == none, 1 == left, 2 == right, 3 == left and right, 4 == middle, 5 == left and middle, 6 == right and middle, 7 == all three
4729
+ // TODO: Support chorded (multiple) button presses on IE 8?
4730
+ if ( button === 1 ) {
4731
+ return 0;
4732
+ } else if ( button === 2 ) {
4733
+ return 2;
4734
+ } else if ( button === 4 ) {
4735
+ return 1;
4736
+ } else {
4737
+ return -1;
4738
+ }
4739
+ } else {
4740
+ return button;
4741
+ }
4742
+ }
4743
+
4744
+
4344
4745
  /**
4345
4746
  * @private
4346
4747
  * @inner
@@ -4358,9 +4759,9 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4358
4759
  currentTime: $.now()
4359
4760
  };
4360
4761
 
4361
- if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
4762
+ if ( updatePointersDown( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
4362
4763
  $.stopEvent( event );
4363
- captureMouse( tracker );
4764
+ capturePointer( tracker, 'mouse' );
4364
4765
  }
4365
4766
 
4366
4767
  if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler ) {
@@ -4379,8 +4780,6 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4379
4780
 
4380
4781
  /**
4381
4782
  * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
4382
- * Only triggered in W3C browsers that don't have setCapture/releaseCapture
4383
- * methods or don't support the new pointer events model.
4384
4783
  * onMouseUp is still attached to the tracked element, so stop propagation to avoid processing twice.
4385
4784
  *
4386
4785
  * @private
@@ -4409,8 +4808,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4409
4808
  currentTime: $.now()
4410
4809
  };
4411
4810
 
4412
- if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
4413
- releaseMouse( tracker );
4811
+ if ( updatePointersUp( tracker, event, [ gPoint ], getStandardizedButton( event.button ) ) ) {
4812
+ releasePointer( tracker, 'mouse' );
4414
4813
  }
4415
4814
  }
4416
4815
 
@@ -4426,8 +4825,6 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4426
4825
 
4427
4826
  /**
4428
4827
  * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
4429
- * Only triggered in W3C browsers that don't have setCapture/releaseCapture
4430
- * methods or don't support the new pointer events model.
4431
4828
  * onMouseMove is still attached to the tracked element, so stop propagation to avoid processing twice.
4432
4829
  *
4433
4830
  * @private
@@ -4464,45 +4861,24 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4464
4861
  * @private
4465
4862
  * @inner
4466
4863
  */
4467
- function onTouchEnter( tracker, event ) {
4864
+ function abortTouchContacts( tracker, event, pointsList ) {
4468
4865
  var i,
4469
- touchCount = event.changedTouches.length,
4470
- gPoints = [];
4866
+ gPointCount = pointsList.getLength(),
4867
+ abortGPoints = [];
4471
4868
 
4472
- for ( i = 0; i < touchCount; i++ ) {
4473
- gPoints.push( {
4474
- id: event.changedTouches[ i ].identifier,
4475
- type: 'touch',
4476
- // isPrimary not set - let the updatePointers functions determine it
4477
- currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
4478
- currentTime: $.now()
4479
- } );
4869
+ for ( i = 0; i < gPointCount; i++ ) {
4870
+ abortGPoints.push( pointsList.getByIndex( i ) );
4480
4871
  }
4481
4872
 
4482
- updatePointersEnter( tracker, event, gPoints );
4483
- }
4484
-
4485
-
4486
- /**
4487
- * @private
4488
- * @inner
4489
- */
4490
- function onTouchLeave( tracker, event ) {
4491
- var i,
4492
- touchCount = event.changedTouches.length,
4493
- gPoints = [];
4494
-
4495
- for ( i = 0; i < touchCount; i++ ) {
4496
- gPoints.push( {
4497
- id: event.changedTouches[ i ].identifier,
4498
- type: 'touch',
4499
- // isPrimary not set - let the updatePointers functions determine it
4500
- currentPos: getMouseAbsolute( event.changedTouches[ i ] ),
4501
- currentTime: $.now()
4502
- } );
4873
+ if ( abortGPoints.length > 0 ) {
4874
+ // simulate touchend
4875
+ updatePointersUp( tracker, event, abortGPoints, 0 ); // 0 means primary button press/release or touch contact
4876
+ // release pointer capture
4877
+ pointsList.captureCount = 1;
4878
+ releasePointer( tracker, 'touch' );
4879
+ // simulate touchleave
4880
+ updatePointersExit( tracker, event, abortGPoints );
4503
4881
  }
4504
-
4505
- updatePointersExit( tracker, event, gPoints );
4506
4882
  }
4507
4883
 
4508
4884
 
@@ -4513,11 +4889,19 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4513
4889
  function onTouchStart( tracker, event ) {
4514
4890
  var time,
4515
4891
  i,
4892
+ j,
4516
4893
  touchCount = event.changedTouches.length,
4517
- gPoints = [];
4894
+ gPoints = [],
4895
+ parentGPoints,
4896
+ pointsList = tracker.getActivePointersListByType( 'touch' );
4518
4897
 
4519
4898
  time = $.now();
4520
4899
 
4900
+ if ( pointsList.getLength() > event.touches.length - touchCount ) {
4901
+ $.console.warn('Tracked touch contact count doesn\'t match event.touches.length. Removing all tracked touch pointers.');
4902
+ abortTouchContacts( tracker, event, pointsList );
4903
+ }
4904
+
4521
4905
  for ( i = 0; i < touchCount; i++ ) {
4522
4906
  gPoints.push( {
4523
4907
  id: event.changedTouches[ i ].identifier,
@@ -4528,13 +4912,29 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4528
4912
  } );
4529
4913
  }
4530
4914
 
4531
- // simulate touchenter if not natively available
4532
- if ( !$.MouseTracker.haveTouchEnter ) {
4533
- updatePointersEnter( tracker, event, gPoints );
4915
+ // simulate touchenter on our tracked element
4916
+ updatePointersEnter( tracker, event, gPoints );
4917
+
4918
+ // simulate touchenter on our tracked element's tracked ancestor elements
4919
+ for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
4920
+ if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
4921
+ parentGPoints = [];
4922
+ for ( j = 0; j < touchCount; j++ ) {
4923
+ parentGPoints.push( {
4924
+ id: event.changedTouches[ j ].identifier,
4925
+ type: 'touch',
4926
+ // isPrimary not set - let the updatePointers functions determine it
4927
+ currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
4928
+ currentTime: time
4929
+ } );
4930
+ }
4931
+ updatePointersEnter( MOUSETRACKERS[ i ], event, parentGPoints );
4932
+ }
4534
4933
  }
4535
4934
 
4536
4935
  if ( updatePointersDown( tracker, event, gPoints, 0 ) ) { // 0 means primary button press/release or touch contact
4537
- // Touch event model start, end, and move events are always captured so we don't need to capture explicitly
4936
+ $.stopEvent( event );
4937
+ capturePointer( tracker, 'touch' );
4538
4938
  }
4539
4939
 
4540
4940
  $.cancelEvent( event );
@@ -4546,10 +4946,34 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4546
4946
  * @inner
4547
4947
  */
4548
4948
  function onTouchEnd( tracker, event ) {
4949
+ handleTouchEnd( tracker, event );
4950
+ }
4951
+
4952
+
4953
+ /**
4954
+ * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
4955
+ * onTouchEnd is still attached to the tracked element, so stop propagation to avoid processing twice.
4956
+ *
4957
+ * @private
4958
+ * @inner
4959
+ */
4960
+ function onTouchEndCaptured( tracker, event ) {
4961
+ handleTouchEnd( tracker, event );
4962
+ $.stopEvent( event );
4963
+ }
4964
+
4965
+
4966
+ /**
4967
+ * @private
4968
+ * @inner
4969
+ */
4970
+ function handleTouchEnd( tracker, event ) {
4549
4971
  var time,
4550
4972
  i,
4973
+ j,
4551
4974
  touchCount = event.changedTouches.length,
4552
- gPoints = [];
4975
+ gPoints = [],
4976
+ parentGPoints;
4553
4977
 
4554
4978
  time = $.now();
4555
4979
 
@@ -4563,13 +4987,28 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4563
4987
  } );
4564
4988
  }
4565
4989
 
4566
- // Touch event model start, end, and move events are always captured so we don't need to release capture.
4567
- // We'll ignore the should-release-capture return value here
4568
- updatePointersUp( tracker, event, gPoints, 0 ); // 0 means primary button press/release or touch contact
4990
+ if ( updatePointersUp( tracker, event, gPoints, 0 ) ) {
4991
+ releasePointer( tracker, 'touch' );
4992
+ }
4993
+
4994
+ // simulate touchleave on our tracked element
4995
+ updatePointersExit( tracker, event, gPoints );
4569
4996
 
4570
- // simulate touchleave if not natively available
4571
- if ( !$.MouseTracker.haveTouchEnter && touchCount > 0 ) {
4572
- updatePointersExit( tracker, event, gPoints );
4997
+ // simulate touchleave on our tracked element's tracked ancestor elements
4998
+ for ( i = 0; i < MOUSETRACKERS.length; i++ ) {
4999
+ if ( MOUSETRACKERS[ i ] !== tracker && MOUSETRACKERS[ i ].isTracking() && isParentChild( MOUSETRACKERS[ i ].element, tracker.element ) ) {
5000
+ parentGPoints = [];
5001
+ for ( j = 0; j < touchCount; j++ ) {
5002
+ parentGPoints.push( {
5003
+ id: event.changedTouches[ j ].identifier,
5004
+ type: 'touch',
5005
+ // isPrimary not set - let the updatePointers functions determine it
5006
+ currentPos: getMouseAbsolute( event.changedTouches[ j ] ),
5007
+ currentTime: time
5008
+ } );
5009
+ }
5010
+ updatePointersExit( MOUSETRACKERS[ i ], event, parentGPoints );
5011
+ }
4573
5012
  }
4574
5013
 
4575
5014
  $.cancelEvent( event );
@@ -4581,6 +5020,28 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4581
5020
  * @inner
4582
5021
  */
4583
5022
  function onTouchMove( tracker, event ) {
5023
+ handleTouchMove( tracker, event );
5024
+ }
5025
+
5026
+
5027
+ /**
5028
+ * This handler is attached to the window object (on the capture phase) to emulate pointer capture.
5029
+ * onTouchMove is still attached to the tracked element, so stop propagation to avoid processing twice.
5030
+ *
5031
+ * @private
5032
+ * @inner
5033
+ */
5034
+ function onTouchMoveCaptured( tracker, event ) {
5035
+ handleTouchMove( tracker, event );
5036
+ $.stopEvent( event );
5037
+ }
5038
+
5039
+
5040
+ /**
5041
+ * @private
5042
+ * @inner
5043
+ */
5044
+ function handleTouchMove( tracker, event ) {
4584
5045
  var i,
4585
5046
  touchCount = event.changedTouches.length,
4586
5047
  gPoints = [];
@@ -4613,7 +5074,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4613
5074
  for ( i = 0; i < touchCount; i++ ) {
4614
5075
  gPoints.push( {
4615
5076
  id: event.changedTouches[ i ].identifier,
4616
- type: 'touch',
5077
+ type: 'touch'
4617
5078
  } );
4618
5079
  }
4619
5080
 
@@ -4647,9 +5108,13 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4647
5108
  * @private
4648
5109
  * @inner
4649
5110
  */
4650
- function onPointerEnter( tracker, event ) {
5111
+ function onPointerOver( tracker, event ) {
4651
5112
  var gPoint;
4652
5113
 
5114
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
5115
+ return;
5116
+ }
5117
+
4653
5118
  gPoint = {
4654
5119
  id: event.pointerId,
4655
5120
  type: getPointerType( event ),
@@ -4666,9 +5131,13 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4666
5131
  * @private
4667
5132
  * @inner
4668
5133
  */
4669
- function onPointerLeave( tracker, event ) {
5134
+ function onPointerOut( tracker, event ) {
4670
5135
  var gPoint;
4671
5136
 
5137
+ if ( event.currentTarget === event.relatedTarget || isParentChild( event.currentTarget, event.relatedTarget ) ) {
5138
+ return;
5139
+ }
5140
+
4672
5141
  gPoint = {
4673
5142
  id: event.pointerId,
4674
5143
  type: getPointerType( event ),
@@ -4697,12 +5166,8 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4697
5166
  };
4698
5167
 
4699
5168
  if ( updatePointersDown( tracker, event, [ gPoint ], event.button ) ) {
4700
- if ( $.MouseTracker.unprefixedPointerEvents ) {
4701
- event.currentTarget.setPointerCapture( event.pointerId );
4702
- } else {
4703
- event.currentTarget.msSetPointerCapture( event.pointerId );
4704
- }
4705
5169
  $.stopEvent( event );
5170
+ capturePointer( tracker, gPoint.type );
4706
5171
  }
4707
5172
 
4708
5173
  if ( tracker.clickHandler || tracker.dblClickHandler || tracker.pressHandler || tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
@@ -4716,6 +5181,31 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4716
5181
  * @inner
4717
5182
  */
4718
5183
  function onPointerUp( tracker, event ) {
5184
+ handlePointerUp( tracker, event );
5185
+ }
5186
+
5187
+
5188
+ /**
5189
+ * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5190
+ * onPointerUp is still attached to the tracked element, so stop propagation to avoid processing twice.
5191
+ *
5192
+ * @private
5193
+ * @inner
5194
+ */
5195
+ function onPointerUpCaptured( tracker, event ) {
5196
+ var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
5197
+ if ( pointsList.getById( event.pointerId ) ) {
5198
+ handlePointerUp( tracker, event );
5199
+ }
5200
+ $.stopEvent( event );
5201
+ }
5202
+
5203
+
5204
+ /**
5205
+ * @private
5206
+ * @inner
5207
+ */
5208
+ function handlePointerUp( tracker, event ) {
4719
5209
  var gPoint;
4720
5210
 
4721
5211
  gPoint = {
@@ -4727,11 +5217,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4727
5217
  };
4728
5218
 
4729
5219
  if ( updatePointersUp( tracker, event, [ gPoint ], event.button ) ) {
4730
- if ( $.MouseTracker.unprefixedPointerEvents ) {
4731
- event.currentTarget.releasePointerCapture( event.pointerId );
4732
- } else {
4733
- event.currentTarget.msReleasePointerCapture( event.pointerId );
4734
- }
5220
+ releasePointer( tracker, gPoint.type );
4735
5221
  }
4736
5222
  }
4737
5223
 
@@ -4741,6 +5227,31 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4741
5227
  * @inner
4742
5228
  */
4743
5229
  function onPointerMove( tracker, event ) {
5230
+ handlePointerMove( tracker, event );
5231
+ }
5232
+
5233
+
5234
+ /**
5235
+ * This handler is attached to the window object (on the capture phase) to emulate mouse capture.
5236
+ * onPointerMove is still attached to the tracked element, so stop propagation to avoid processing twice.
5237
+ *
5238
+ * @private
5239
+ * @inner
5240
+ */
5241
+ function onPointerMoveCaptured( tracker, event ) {
5242
+ var pointsList = tracker.getActivePointersListByType( getPointerType( event ) );
5243
+ if ( pointsList.getById( event.pointerId ) ) {
5244
+ handlePointerMove( tracker, event );
5245
+ }
5246
+ $.stopEvent( event );
5247
+ }
5248
+
5249
+
5250
+ /**
5251
+ * @private
5252
+ * @inner
5253
+ */
5254
+ function handlePointerMove( tracker, event ) {
4744
5255
  // Pointer changed coordinates, button state, pressure, tilt, or contact geometry (e.g. width and height)
4745
5256
  var gPoint;
4746
5257
 
@@ -4765,7 +5276,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4765
5276
 
4766
5277
  gPoint = {
4767
5278
  id: event.pointerId,
4768
- type: getPointerType( event ),
5279
+ type: getPointerType( event )
4769
5280
  };
4770
5281
 
4771
5282
  updatePointersCancel( tracker, event, [ gPoint ] );
@@ -4891,6 +5402,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4891
5402
  pointerType: curGPoint.type,
4892
5403
  position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
4893
5404
  buttons: pointsList.buttons,
5405
+ pointers: tracker.getActivePointerCount(),
4894
5406
  insideElementPressed: curGPoint.insideElementPressed,
4895
5407
  buttonDownAny: pointsList.buttons !== 0,
4896
5408
  isTouchEvent: curGPoint.type === 'touch',
@@ -4954,6 +5466,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4954
5466
  pointerType: curGPoint.type,
4955
5467
  position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
4956
5468
  buttons: pointsList.buttons,
5469
+ pointers: tracker.getActivePointerCount(),
4957
5470
  insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false,
4958
5471
  buttonDownAny: pointsList.buttons !== 0,
4959
5472
  isTouchEvent: curGPoint.type === 'touch',
@@ -4982,7 +5495,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
4982
5495
  * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
4983
5496
  * Gesture points associated with the event.
4984
5497
  * @param {Number} buttonChanged
4985
- * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser.
5498
+ * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
4986
5499
  * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
4987
5500
  * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
4988
5501
  *
@@ -5000,30 +5513,71 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5000
5513
  if ( typeof event.buttons !== 'undefined' ) {
5001
5514
  pointsList.buttons = event.buttons;
5002
5515
  } else {
5003
- if ( buttonChanged === 0 ) {
5004
- // Primary
5005
- pointsList.buttons |= 1;
5006
- } else if ( buttonChanged === 1 ) {
5007
- // Aux
5008
- pointsList.buttons |= 4;
5009
- } else if ( buttonChanged === 2 ) {
5010
- // Secondary
5011
- pointsList.buttons |= 2;
5012
- } else if ( buttonChanged === 3 ) {
5013
- // X1 (Back)
5014
- pointsList.buttons |= 8;
5015
- } else if ( buttonChanged === 4 ) {
5016
- // X2 (Forward)
5017
- pointsList.buttons |= 16;
5018
- } else if ( buttonChanged === 5 ) {
5019
- // Pen Eraser
5020
- pointsList.buttons |= 32;
5516
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
5517
+ if ( buttonChanged === 0 ) {
5518
+ // Primary
5519
+ pointsList.buttons += 1;
5520
+ } else if ( buttonChanged === 1 ) {
5521
+ // Aux
5522
+ pointsList.buttons += 4;
5523
+ } else if ( buttonChanged === 2 ) {
5524
+ // Secondary
5525
+ pointsList.buttons += 2;
5526
+ } else if ( buttonChanged === 3 ) {
5527
+ // X1 (Back)
5528
+ pointsList.buttons += 8;
5529
+ } else if ( buttonChanged === 4 ) {
5530
+ // X2 (Forward)
5531
+ pointsList.buttons += 16;
5532
+ } else if ( buttonChanged === 5 ) {
5533
+ // Pen Eraser
5534
+ pointsList.buttons += 32;
5535
+ }
5536
+ } else {
5537
+ if ( buttonChanged === 0 ) {
5538
+ // Primary
5539
+ pointsList.buttons |= 1;
5540
+ } else if ( buttonChanged === 1 ) {
5541
+ // Aux
5542
+ pointsList.buttons |= 4;
5543
+ } else if ( buttonChanged === 2 ) {
5544
+ // Secondary
5545
+ pointsList.buttons |= 2;
5546
+ } else if ( buttonChanged === 3 ) {
5547
+ // X1 (Back)
5548
+ pointsList.buttons |= 8;
5549
+ } else if ( buttonChanged === 4 ) {
5550
+ // X2 (Forward)
5551
+ pointsList.buttons |= 16;
5552
+ } else if ( buttonChanged === 5 ) {
5553
+ // Pen Eraser
5554
+ pointsList.buttons |= 32;
5555
+ }
5021
5556
  }
5022
5557
  }
5023
5558
 
5024
5559
  // Only capture and track primary button, pen, and touch contacts
5025
- //if ( buttonChanged !== 0 ) {
5026
- if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above
5560
+ if ( buttonChanged !== 0 ) {
5561
+ // Aux Press
5562
+ if ( tracker.nonPrimaryPressHandler ) {
5563
+ propagate = tracker.nonPrimaryPressHandler(
5564
+ {
5565
+ eventSource: tracker,
5566
+ pointerType: gPoints[ 0 ].type,
5567
+ position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
5568
+ button: buttonChanged,
5569
+ buttons: pointsList.buttons,
5570
+ isTouchEvent: gPoints[ 0 ].type === 'touch',
5571
+ originalEvent: event,
5572
+ preventDefaultAction: false,
5573
+ userData: tracker.userData
5574
+ }
5575
+ );
5576
+ if ( propagate === false ) {
5577
+ $.cancelEvent( event );
5578
+ }
5579
+ }
5580
+
5027
5581
  return false;
5028
5582
  }
5029
5583
 
@@ -5053,6 +5607,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5053
5607
  }
5054
5608
 
5055
5609
  pointsList.contacts++;
5610
+ //$.console.log('contacts++ ', pointsList.contacts);
5056
5611
 
5057
5612
  if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
5058
5613
  $.MouseTracker.gesturePointVelocityTracker.addPoint( tracker, curGPoint );
@@ -5102,7 +5657,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5102
5657
  * @param {Array.<OpenSeadragon.MouseTracker.GesturePoint>} gPoints
5103
5658
  * Gesture points associated with the event.
5104
5659
  * @param {Number} buttonChanged
5105
- * The button involved in the event: -1: none, 0: primary, 1: aux, 2: secondary, 3: X1, 4: X2, 5: pen eraser.
5660
+ * The button involved in the event: -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
5106
5661
  * Note on chorded button presses (a button pressed when another button is already pressed): In the W3C Pointer Events model,
5107
5662
  * only one pointerdown/pointerup event combo is fired. Chorded button state changes instead fire pointermove events.
5108
5663
  *
@@ -5126,30 +5681,71 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5126
5681
  if ( typeof event.buttons !== 'undefined' ) {
5127
5682
  pointsList.buttons = event.buttons;
5128
5683
  } else {
5129
- if ( buttonChanged === 0 ) {
5130
- // Primary
5131
- pointsList.buttons ^= ~1;
5132
- } else if ( buttonChanged === 1 ) {
5133
- // Aux
5134
- pointsList.buttons ^= ~4;
5135
- } else if ( buttonChanged === 2 ) {
5136
- // Secondary
5137
- pointsList.buttons ^= ~2;
5138
- } else if ( buttonChanged === 3 ) {
5139
- // X1 (Back)
5140
- pointsList.buttons ^= ~8;
5141
- } else if ( buttonChanged === 4 ) {
5142
- // X2 (Forward)
5143
- pointsList.buttons ^= ~16;
5144
- } else if ( buttonChanged === 5 ) {
5145
- // Pen Eraser
5146
- pointsList.buttons ^= ~32;
5684
+ if ( $.Browser.vendor === $.BROWSERS.IE && $.Browser.version < 9 ) {
5685
+ if ( buttonChanged === 0 ) {
5686
+ // Primary
5687
+ pointsList.buttons -= 1;
5688
+ } else if ( buttonChanged === 1 ) {
5689
+ // Aux
5690
+ pointsList.buttons -= 4;
5691
+ } else if ( buttonChanged === 2 ) {
5692
+ // Secondary
5693
+ pointsList.buttons -= 2;
5694
+ } else if ( buttonChanged === 3 ) {
5695
+ // X1 (Back)
5696
+ pointsList.buttons -= 8;
5697
+ } else if ( buttonChanged === 4 ) {
5698
+ // X2 (Forward)
5699
+ pointsList.buttons -= 16;
5700
+ } else if ( buttonChanged === 5 ) {
5701
+ // Pen Eraser
5702
+ pointsList.buttons -= 32;
5703
+ }
5704
+ } else {
5705
+ if ( buttonChanged === 0 ) {
5706
+ // Primary
5707
+ pointsList.buttons ^= ~1;
5708
+ } else if ( buttonChanged === 1 ) {
5709
+ // Aux
5710
+ pointsList.buttons ^= ~4;
5711
+ } else if ( buttonChanged === 2 ) {
5712
+ // Secondary
5713
+ pointsList.buttons ^= ~2;
5714
+ } else if ( buttonChanged === 3 ) {
5715
+ // X1 (Back)
5716
+ pointsList.buttons ^= ~8;
5717
+ } else if ( buttonChanged === 4 ) {
5718
+ // X2 (Forward)
5719
+ pointsList.buttons ^= ~16;
5720
+ } else if ( buttonChanged === 5 ) {
5721
+ // Pen Eraser
5722
+ pointsList.buttons ^= ~32;
5723
+ }
5147
5724
  }
5148
5725
  }
5149
5726
 
5150
5727
  // Only capture and track primary button, pen, and touch contacts
5151
- //if ( buttonChanged !== 0 ) {
5152
- if ( buttonChanged !== 0 && buttonChanged !== 1 ) { //TODO Remove this IE8 compatibility and use the commented line above
5728
+ if ( buttonChanged !== 0 ) {
5729
+ // Aux Release
5730
+ if ( tracker.nonPrimaryReleaseHandler ) {
5731
+ propagate = tracker.nonPrimaryReleaseHandler(
5732
+ {
5733
+ eventSource: tracker,
5734
+ pointerType: gPoints[ 0 ].type,
5735
+ position: getPointRelativeToAbsolute( gPoints[ 0 ].currentPos, tracker.element ),
5736
+ button: buttonChanged,
5737
+ buttons: pointsList.buttons,
5738
+ isTouchEvent: gPoints[ 0 ].type === 'touch',
5739
+ originalEvent: event,
5740
+ preventDefaultAction: false,
5741
+ userData: tracker.userData
5742
+ }
5743
+ );
5744
+ if ( propagate === false ) {
5745
+ $.cancelEvent( event );
5746
+ }
5747
+ }
5748
+
5153
5749
  return false;
5154
5750
  }
5155
5751
 
@@ -5179,6 +5775,7 @@ $.EventSource.prototype = /** @lends OpenSeadragon.EventSource.prototype */{
5179
5775
  // Pointer was activated in our element but could have been removed in any element since events are captured to our element
5180
5776
 
5181
5777
  pointsList.contacts--;
5778
+ //$.console.log('contacts-- ', pointsList.contacts);
5182
5779
 
5183
5780
  if ( tracker.dragHandler || tracker.dragEndHandler || tracker.pinchHandler ) {
5184
5781
  $.MouseTracker.gesturePointVelocityTracker.removePoint( tracker, updateGPoint );
@@ -6079,14 +6676,6 @@ $.Viewer = function( options ) {
6079
6676
  * @memberof OpenSeadragon.Viewer#
6080
6677
  */
6081
6678
  container: null,
6082
- /**
6083
- * A &lt;textarea&gt; element, the element where keyboard events are handled.<br><br>
6084
- * Child element of {@link OpenSeadragon.Viewer#container},
6085
- * positioned below {@link OpenSeadragon.Viewer#canvas}.
6086
- * @member {Element} keyboardCommandArea
6087
- * @memberof OpenSeadragon.Viewer#
6088
- */
6089
- keyboardCommandArea: null,
6090
6679
  /**
6091
6680
  * A &lt;div&gt; element, the element where user-input events are handled for panning and zooming.<br><br>
6092
6681
  * Child element of {@link OpenSeadragon.Viewer#container},
@@ -6234,7 +6823,6 @@ $.Viewer = function( options ) {
6234
6823
 
6235
6824
  this.element = this.element || document.getElementById( this.id );
6236
6825
  this.canvas = $.makeNeutralElement( "div" );
6237
- this.keyboardCommandArea = $.makeNeutralElement( "textarea" );
6238
6826
  this.drawersContainer = $.makeNeutralElement( "div" );
6239
6827
  this.overlaysContainer = $.makeNeutralElement( "div" );
6240
6828
 
@@ -6246,13 +6834,9 @@ $.Viewer = function( options ) {
6246
6834
  style.position = "absolute";
6247
6835
  style.top = "0px";
6248
6836
  style.left = "0px";
6249
- // Disable browser default touch handling
6250
- if (style["touch-action"] !== undefined) {
6251
- style["touch-action"] = "none";
6252
- } else if (style["-ms-touch-action"] !== undefined) {
6253
- style["-ms-touch-action"] = "none";
6254
- }
6255
6837
  }(this.canvas.style));
6838
+ $.setElementTouchActionNone( this.canvas );
6839
+ this.canvas.tabIndex = options.tabIndex || 0;
6256
6840
 
6257
6841
  //the container is created through applying the ControlDock constructor above
6258
6842
  this.container.className = "openseadragon-container";
@@ -6266,19 +6850,7 @@ $.Viewer = function( options ) {
6266
6850
  style.textAlign = "left"; // needed to protect against
6267
6851
  }( this.container.style ));
6268
6852
 
6269
- this.keyboardCommandArea.className = "keyboard-command-area";
6270
- (function( style ){
6271
- style.width = "100%";
6272
- style.height = "100%";
6273
- style.overflow = "hidden";
6274
- style.position = "absolute";
6275
- style.top = "0px";
6276
- style.left = "0px";
6277
- style.resize = "none";
6278
- }( this.keyboardCommandArea.style ));
6279
-
6280
6853
  this.container.insertBefore( this.canvas, this.container.firstChild );
6281
- this.container.insertBefore( this.keyboardCommandArea, this.container.firstChild );
6282
6854
  this.element.appendChild( this.container );
6283
6855
  this.canvas.appendChild( this.drawersContainer );
6284
6856
  this.canvas.appendChild( this.overlaysContainer );
@@ -6291,96 +6863,39 @@ $.Viewer = function( options ) {
6291
6863
  this.bodyOverflow = document.body.style.overflow;
6292
6864
  this.docOverflow = document.documentElement.style.overflow;
6293
6865
 
6294
- this.keyboardCommandArea.innerTracker = new $.MouseTracker({
6295
- _this : this,
6296
- element: this.keyboardCommandArea,
6297
- focusHandler: function( event ){
6298
- if ( !event.preventDefaultAction ) {
6299
- var point = $.getElementPosition( this.element );
6300
- window.scrollTo( 0, point.y );
6301
- }
6302
- },
6303
-
6304
- keyHandler: function( event ){
6305
- if ( !event.preventDefaultAction ) {
6306
- switch( event.keyCode ){
6307
- case 61://=|+
6308
- _this.viewport.zoomBy(1.1);
6309
- _this.viewport.applyConstraints();
6310
- return false;
6311
- case 45://-|_
6312
- _this.viewport.zoomBy(0.9);
6313
- _this.viewport.applyConstraints();
6314
- return false;
6315
- case 48://0|)
6316
- _this.viewport.goHome();
6317
- _this.viewport.applyConstraints();
6318
- return false;
6319
- case 119://w
6320
- case 87://W
6321
- case 38://up arrow
6322
- if ( event.shift ) {
6323
- _this.viewport.zoomBy(1.1);
6324
- } else {
6325
- _this.viewport.panBy(new $.Point(0, -0.05));
6326
- }
6327
- _this.viewport.applyConstraints();
6328
- return false;
6329
- case 115://s
6330
- case 83://S
6331
- case 40://down arrow
6332
- if ( event.shift ) {
6333
- _this.viewport.zoomBy(0.9);
6334
- } else {
6335
- _this.viewport.panBy(new $.Point(0, 0.05));
6336
- }
6337
- _this.viewport.applyConstraints();
6338
- return false;
6339
- case 97://a
6340
- case 37://left arrow
6341
- _this.viewport.panBy(new $.Point(-0.05, 0));
6342
- _this.viewport.applyConstraints();
6343
- return false;
6344
- case 100://d
6345
- case 39://right arrow
6346
- _this.viewport.panBy(new $.Point(0.05, 0));
6347
- _this.viewport.applyConstraints();
6348
- return false;
6349
- default:
6350
- //console.log( 'navigator keycode %s', event.keyCode );
6351
- return true;
6352
- }
6353
- }
6354
- }
6355
- }).setTracking( true ); // default state
6356
-
6357
-
6358
6866
  this.innerTracker = new $.MouseTracker({
6359
- element: this.canvas,
6360
- clickTimeThreshold: this.clickTimeThreshold,
6361
- clickDistThreshold: this.clickDistThreshold,
6362
- dblClickTimeThreshold: this.dblClickTimeThreshold,
6363
- dblClickDistThreshold: this.dblClickDistThreshold,
6364
- clickHandler: $.delegate( this, onCanvasClick ),
6365
- dblClickHandler: $.delegate( this, onCanvasDblClick ),
6366
- dragHandler: $.delegate( this, onCanvasDrag ),
6367
- dragEndHandler: $.delegate( this, onCanvasDragEnd ),
6368
- releaseHandler: $.delegate( this, onCanvasRelease ),
6369
- scrollHandler: $.delegate( this, onCanvasScroll ),
6370
- pinchHandler: $.delegate( this, onCanvasPinch )
6371
- }).setTracking( this.mouseNavEnabled ? true : false ); // default state
6867
+ element: this.canvas,
6868
+ startDisabled: this.mouseNavEnabled ? false : true,
6869
+ clickTimeThreshold: this.clickTimeThreshold,
6870
+ clickDistThreshold: this.clickDistThreshold,
6871
+ dblClickTimeThreshold: this.dblClickTimeThreshold,
6872
+ dblClickDistThreshold: this.dblClickDistThreshold,
6873
+ keyDownHandler: $.delegate( this, onCanvasKeyDown ),
6874
+ keyHandler: $.delegate( this, onCanvasKeyPress ),
6875
+ clickHandler: $.delegate( this, onCanvasClick ),
6876
+ dblClickHandler: $.delegate( this, onCanvasDblClick ),
6877
+ dragHandler: $.delegate( this, onCanvasDrag ),
6878
+ dragEndHandler: $.delegate( this, onCanvasDragEnd ),
6879
+ enterHandler: $.delegate( this, onCanvasEnter ),
6880
+ exitHandler: $.delegate( this, onCanvasExit ),
6881
+ pressHandler: $.delegate( this, onCanvasPress ),
6882
+ releaseHandler: $.delegate( this, onCanvasRelease ),
6883
+ nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ),
6884
+ nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ),
6885
+ scrollHandler: $.delegate( this, onCanvasScroll ),
6886
+ pinchHandler: $.delegate( this, onCanvasPinch )
6887
+ });
6372
6888
 
6373
6889
  this.outerTracker = new $.MouseTracker({
6374
6890
  element: this.container,
6891
+ startDisabled: this.mouseNavEnabled ? false : true,
6375
6892
  clickTimeThreshold: this.clickTimeThreshold,
6376
6893
  clickDistThreshold: this.clickDistThreshold,
6377
6894
  dblClickTimeThreshold: this.dblClickTimeThreshold,
6378
6895
  dblClickDistThreshold: this.dblClickDistThreshold,
6379
6896
  enterHandler: $.delegate( this, onContainerEnter ),
6380
- exitHandler: $.delegate( this, onContainerExit ),
6381
- pressHandler: $.delegate( this, onContainerPress ),
6382
- releaseHandler: $.delegate( this, onContainerRelease )
6383
- }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking
6897
+ exitHandler: $.delegate( this, onContainerExit )
6898
+ });
6384
6899
 
6385
6900
  if( this.toolbar ){
6386
6901
  this.toolbar = new $.ControlDock({ element: this.toolbar });
@@ -6500,6 +7015,12 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6500
7015
  * @fires OpenSeadragon.Viewer.event:close
6501
7016
  */
6502
7017
  close: function ( ) {
7018
+
7019
+ if ( !THIS[ this.hash ] ) {
7020
+ //this viewer has already been destroyed: returning immediately
7021
+ return this;
7022
+ }
7023
+
6503
7024
  if ( this._updateRequestId !== null ) {
6504
7025
  $.cancelAnimationFrame( this._updateRequestId );
6505
7026
  this._updateRequestId = null;
@@ -6509,9 +7030,17 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6509
7030
  this.navigator.close();
6510
7031
  }
6511
7032
 
6512
- this.clearOverlays();
7033
+ if( ! this.preserveOverlays)
7034
+ {
7035
+ this.clearOverlays();
7036
+ this.overlaysContainer.innerHTML = "";
7037
+ }
7038
+
6513
7039
  this.drawersContainer.innerHTML = "";
6514
- this.overlaysContainer.innerHTML = "";
7040
+
7041
+ if ( this.drawer ) {
7042
+ this.drawer.destroy();
7043
+ }
6515
7044
 
6516
7045
  this.source = null;
6517
7046
  this.drawer = null;
@@ -6539,13 +7068,26 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6539
7068
 
6540
7069
 
6541
7070
  /**
6542
- * Function to destroy the viewer and clean up everything created by
6543
- * OpenSeadragon.
7071
+ * Function to destroy the viewer and clean up everything created by OpenSeadragon.
7072
+ *
7073
+ * Example:
7074
+ * var viewer = OpenSeadragon({
7075
+ * [...]
7076
+ * });
7077
+ *
7078
+ * //when you are done with the viewer:
7079
+ * viewer.destroy();
7080
+ * viewer = null; //important
7081
+ *
6544
7082
  * @function
6545
7083
  */
6546
7084
  destroy: function( ) {
6547
7085
  this.close();
6548
7086
 
7087
+ //TODO: implement this...
7088
+ //this.unbindSequenceControls()
7089
+ //this.unbindStandardControls()
7090
+
6549
7091
  this.removeAllHandlers();
6550
7092
 
6551
7093
  // Go through top element (passed to us) and remove all children
@@ -6558,9 +7100,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6558
7100
  }
6559
7101
 
6560
7102
  // destroy the mouse trackers
6561
- if (this.keyboardCommandArea){
6562
- this.keyboardCommandArea.innerTracker.destroy();
6563
- }
6564
7103
  if (this.innerTracker){
6565
7104
  this.innerTracker.destroy();
6566
7105
  }
@@ -6568,9 +7107,11 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
6568
7107
  this.outerTracker.destroy();
6569
7108
  }
6570
7109
 
7110
+ THIS[ this.hash ] = null;
7111
+ delete THIS[ this.hash ];
7112
+
6571
7113
  // clear all our references to dom objects
6572
7114
  this.canvas = null;
6573
- this.keyboardCommandArea = null;
6574
7115
  this.container = null;
6575
7116
 
6576
7117
  // clear our reference to the main element - they will need to pass it in again, creating a new viewer
@@ -7887,7 +8428,8 @@ function openTileSource( viewer, source ) {
7887
8428
  degrees: _this.degrees //,
7888
8429
  //TODO: figure out how to support these in a way that makes sense
7889
8430
  //minZoomLevel: this.minZoomLevel,
7890
- //maxZoomLevel: this.maxZoomLevel
8431
+ //maxZoomLevel: this.maxZoomLevel,
8432
+ //homeFillsViewer: this.homeFillsViewer
7891
8433
  });
7892
8434
  } else {
7893
8435
  if( source ){
@@ -7907,7 +8449,9 @@ function openTileSource( viewer, source ) {
7907
8449
  minZoomLevel: _this.minZoomLevel,
7908
8450
  maxZoomLevel: _this.maxZoomLevel,
7909
8451
  viewer: _this,
7910
- degrees: _this.degrees
8452
+ degrees: _this.degrees,
8453
+ navigatorRotate: _this.navigatorRotate,
8454
+ homeFillsViewer: _this.homeFillsViewer
7911
8455
  });
7912
8456
  }
7913
8457
 
@@ -7915,6 +8459,10 @@ function openTileSource( viewer, source ) {
7915
8459
  _this.viewport.resetContentSize( _this.source.dimensions );
7916
8460
  }
7917
8461
 
8462
+ if( _this.preserveOverlays ){
8463
+ _this.overlays = _this.currentOverlays;
8464
+ }
8465
+
7918
8466
  _this.source.overlays = _this.source.overlays || [];
7919
8467
 
7920
8468
  _this.drawer = new $.Drawer({
@@ -7974,7 +8522,8 @@ function openTileSource( viewer, source ) {
7974
8522
  tileSources: source,
7975
8523
  tileHost: _this.tileHost,
7976
8524
  prefixUrl: _this.prefixUrl,
7977
- viewer: _this
8525
+ viewer: _this,
8526
+ navigatorRotate: _this.navigatorRotate
7978
8527
  });
7979
8528
  }
7980
8529
  }
@@ -8208,9 +8757,102 @@ function onBlur(){
8208
8757
 
8209
8758
  }
8210
8759
 
8211
- function onCanvasClick( event ) {
8212
- var gestureSettings;
8213
-
8760
+ function onCanvasKeyDown( event ) {
8761
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
8762
+ switch( event.keyCode ){
8763
+ case 38://up arrow
8764
+ if ( event.shift ) {
8765
+ this.viewport.zoomBy(1.1);
8766
+ } else {
8767
+ this.viewport.panBy(new $.Point(0, -0.05));
8768
+ }
8769
+ this.viewport.applyConstraints();
8770
+ return false;
8771
+ case 40://down arrow
8772
+ if ( event.shift ) {
8773
+ this.viewport.zoomBy(0.9);
8774
+ } else {
8775
+ this.viewport.panBy(new $.Point(0, 0.05));
8776
+ }
8777
+ this.viewport.applyConstraints();
8778
+ return false;
8779
+ case 37://left arrow
8780
+ this.viewport.panBy(new $.Point(-0.05, 0));
8781
+ this.viewport.applyConstraints();
8782
+ return false;
8783
+ case 39://right arrow
8784
+ this.viewport.panBy(new $.Point(0.05, 0));
8785
+ this.viewport.applyConstraints();
8786
+ return false;
8787
+ default:
8788
+ //console.log( 'navigator keycode %s', event.keyCode );
8789
+ return true;
8790
+ }
8791
+ } else {
8792
+ return true;
8793
+ }
8794
+ }
8795
+
8796
+ function onCanvasKeyPress( event ) {
8797
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
8798
+ switch( event.keyCode ){
8799
+ case 61://=|+
8800
+ this.viewport.zoomBy(1.1);
8801
+ this.viewport.applyConstraints();
8802
+ return false;
8803
+ case 45://-|_
8804
+ this.viewport.zoomBy(0.9);
8805
+ this.viewport.applyConstraints();
8806
+ return false;
8807
+ case 48://0|)
8808
+ this.viewport.goHome();
8809
+ this.viewport.applyConstraints();
8810
+ return false;
8811
+ case 119://w
8812
+ case 87://W
8813
+ if ( event.shift ) {
8814
+ this.viewport.zoomBy(1.1);
8815
+ } else {
8816
+ this.viewport.panBy(new $.Point(0, -0.05));
8817
+ }
8818
+ this.viewport.applyConstraints();
8819
+ return false;
8820
+ case 115://s
8821
+ case 83://S
8822
+ if ( event.shift ) {
8823
+ this.viewport.zoomBy(0.9);
8824
+ } else {
8825
+ this.viewport.panBy(new $.Point(0, 0.05));
8826
+ }
8827
+ this.viewport.applyConstraints();
8828
+ return false;
8829
+ case 97://a
8830
+ this.viewport.panBy(new $.Point(-0.05, 0));
8831
+ this.viewport.applyConstraints();
8832
+ return false;
8833
+ case 100://d
8834
+ this.viewport.panBy(new $.Point(0.05, 0));
8835
+ this.viewport.applyConstraints();
8836
+ return false;
8837
+ default:
8838
+ //console.log( 'navigator keycode %s', event.keyCode );
8839
+ return true;
8840
+ }
8841
+ } else {
8842
+ return true;
8843
+ }
8844
+ }
8845
+
8846
+ function onCanvasClick( event ) {
8847
+ var gestureSettings;
8848
+
8849
+ var haveKeyboardFocus = document.activeElement == this.canvas;
8850
+
8851
+ // If we don't have keyboard focus, request it.
8852
+ if ( !haveKeyboardFocus ) {
8853
+ this.canvas.focus();
8854
+ }
8855
+
8214
8856
  if ( !event.preventDefaultAction && this.viewport && event.quick ) {
8215
8857
  gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
8216
8858
  if ( gestureSettings.clickToZoom ) {
@@ -8327,8 +8969,8 @@ function onCanvasDragEnd( event ) {
8327
8969
  if ( !event.preventDefaultAction && this.viewport ) {
8328
8970
  gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
8329
8971
  if ( gestureSettings.flickEnabled && event.speed >= gestureSettings.flickMinSpeed ) {
8330
- var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction ) ),
8331
- amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction ) ),
8972
+ var amplitudeX = gestureSettings.flickMomentum * ( event.speed * Math.cos( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
8973
+ amplitudeY = gestureSettings.flickMomentum * ( event.speed * Math.sin( event.direction - (Math.PI / 180 * this.viewport.degrees) ) ),
8332
8974
  center = this.viewport.pixelFromPoint( this.viewport.getCenter( true ) ),
8333
8975
  target = this.viewport.pointFromPixel( new $.Point( center.x - amplitudeX, center.y - amplitudeY ) );
8334
8976
  if( !this.panHorizontal ) {
@@ -8338,8 +8980,8 @@ function onCanvasDragEnd( event ) {
8338
8980
  target.y = center.y;
8339
8981
  }
8340
8982
  this.viewport.panTo( target, false );
8341
- this.viewport.applyConstraints();
8342
8983
  }
8984
+ this.viewport.applyConstraints();
8343
8985
  }
8344
8986
  /**
8345
8987
  * Raised when a mouse or touch drag operation ends on the {@link OpenSeadragon.Viewer#canvas} element.
@@ -8366,24 +9008,102 @@ function onCanvasDragEnd( event ) {
8366
9008
  });
8367
9009
  }
8368
9010
 
8369
- function onCanvasRelease( event ) {
8370
- var gestureSettings;
9011
+ function onCanvasEnter( event ) {
9012
+ /**
9013
+ * Raised when a pointer enters the {@link OpenSeadragon.Viewer#canvas} element.
9014
+ *
9015
+ * @event canvas-enter
9016
+ * @memberof OpenSeadragon.Viewer
9017
+ * @type {object}
9018
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9019
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9020
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9021
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9022
+ * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9023
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
9024
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9025
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
9026
+ * @property {Object} originalEvent - The original DOM event.
9027
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9028
+ */
9029
+ this.raiseEvent( 'canvas-enter', {
9030
+ tracker: event.eventSource,
9031
+ pointerType: event.pointerType,
9032
+ position: event.position,
9033
+ buttons: event.buttons,
9034
+ pointers: event.pointers,
9035
+ insideElementPressed: event.insideElementPressed,
9036
+ buttonDownAny: event.buttonDownAny,
9037
+ originalEvent: event.originalEvent
9038
+ });
9039
+ }
8371
9040
 
8372
- if ( event.insideElementPressed && this.viewport ) {
8373
- gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9041
+ function onCanvasExit( event ) {
9042
+ /**
9043
+ * Raised when a pointer leaves the {@link OpenSeadragon.Viewer#canvas} element.
9044
+ *
9045
+ * @event canvas-exit
9046
+ * @memberof OpenSeadragon.Viewer
9047
+ * @type {object}
9048
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9049
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9050
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9051
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9052
+ * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9053
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
9054
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9055
+ * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
9056
+ * @property {Object} originalEvent - The original DOM event.
9057
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9058
+ */
9059
+ this.raiseEvent( 'canvas-exit', {
9060
+ tracker: event.eventSource,
9061
+ pointerType: event.pointerType,
9062
+ position: event.position,
9063
+ buttons: event.buttons,
9064
+ pointers: event.pointers,
9065
+ insideElementPressed: event.insideElementPressed,
9066
+ buttonDownAny: event.buttonDownAny,
9067
+ originalEvent: event.originalEvent
9068
+ });
9069
+ }
8374
9070
 
8375
- if ( !gestureSettings.flickEnabled ) {
8376
- this.viewport.applyConstraints();
8377
- }
8378
- }
9071
+ function onCanvasPress( event ) {
8379
9072
  /**
8380
- * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
9073
+ * Raised when the primary mouse button is pressed or touch starts on the {@link OpenSeadragon.Viewer#canvas} element.
9074
+ *
9075
+ * @event canvas-press
9076
+ * @memberof OpenSeadragon.Viewer
9077
+ * @type {object}
9078
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9079
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9080
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9081
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9082
+ * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
9083
+ * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
9084
+ * @property {Object} originalEvent - The original DOM event.
9085
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9086
+ */
9087
+ this.raiseEvent( 'canvas-press', {
9088
+ tracker: event.eventSource,
9089
+ pointerType: event.pointerType,
9090
+ position: event.position,
9091
+ insideElementPressed: event.insideElementPressed,
9092
+ insideElementReleased: event.insideElementReleased,
9093
+ originalEvent: event.originalEvent
9094
+ });
9095
+ }
9096
+
9097
+ function onCanvasRelease( event ) {
9098
+ /**
9099
+ * Raised when the primary mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#canvas} element.
8381
9100
  *
8382
9101
  * @event canvas-release
8383
9102
  * @memberof OpenSeadragon.Viewer
8384
9103
  * @type {object}
8385
9104
  * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8386
9105
  * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9106
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
8387
9107
  * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8388
9108
  * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8389
9109
  * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
@@ -8392,6 +9112,7 @@ function onCanvasRelease( event ) {
8392
9112
  */
8393
9113
  this.raiseEvent( 'canvas-release', {
8394
9114
  tracker: event.eventSource,
9115
+ pointerType: event.pointerType,
8395
9116
  position: event.position,
8396
9117
  insideElementPressed: event.insideElementPressed,
8397
9118
  insideElementReleased: event.insideElementReleased,
@@ -8399,6 +9120,62 @@ function onCanvasRelease( event ) {
8399
9120
  });
8400
9121
  }
8401
9122
 
9123
+ function onCanvasNonPrimaryPress( event ) {
9124
+ /**
9125
+ * Raised when any non-primary pointer button is pressed on the {@link OpenSeadragon.Viewer#canvas} element.
9126
+ *
9127
+ * @event canvas-nonprimary-press
9128
+ * @memberof OpenSeadragon.Viewer
9129
+ * @type {object}
9130
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9131
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9132
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9133
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9134
+ * @property {Number} button - Button which caused the event.
9135
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
9136
+ * @property {Number} buttons - Current buttons pressed.
9137
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9138
+ * @property {Object} originalEvent - The original DOM event.
9139
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9140
+ */
9141
+ this.raiseEvent( 'canvas-nonprimary-press', {
9142
+ tracker: event.eventSource,
9143
+ position: event.position,
9144
+ pointerType: event.pointerType,
9145
+ button: event.button,
9146
+ buttons: event.buttons,
9147
+ originalEvent: event.originalEvent
9148
+ });
9149
+ }
9150
+
9151
+ function onCanvasNonPrimaryRelease( event ) {
9152
+ /**
9153
+ * Raised when any non-primary pointer button is released on the {@link OpenSeadragon.Viewer#canvas} element.
9154
+ *
9155
+ * @event canvas-nonprimary-release
9156
+ * @memberof OpenSeadragon.Viewer
9157
+ * @type {object}
9158
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9159
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
9160
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9161
+ * @property {String} pointerType - "mouse", "touch", "pen", etc.
9162
+ * @property {Number} button - Button which caused the event.
9163
+ * -1: none, 0: primary/left, 1: aux/middle, 2: secondary/right, 3: X1/back, 4: X2/forward, 5: pen eraser.
9164
+ * @property {Number} buttons - Current buttons pressed.
9165
+ * Combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9166
+ * @property {Object} originalEvent - The original DOM event.
9167
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9168
+ */
9169
+ this.raiseEvent( 'canvas-nonprimary-release', {
9170
+ tracker: event.eventSource,
9171
+ position: event.position,
9172
+ pointerType: event.pointerType,
9173
+ button: event.button,
9174
+ buttons: event.buttons,
9175
+ originalEvent: event.originalEvent
9176
+ });
9177
+ }
9178
+
8402
9179
  function onCanvasPinch( event ) {
8403
9180
  var gestureSettings,
8404
9181
  centerPt,
@@ -8421,6 +9198,14 @@ function onCanvasPinch( event ) {
8421
9198
  this.viewport.panBy( panByPt, true );
8422
9199
  this.viewport.applyConstraints();
8423
9200
  }
9201
+ if ( gestureSettings.pinchRotate ) {
9202
+ // Pinch rotate
9203
+ var angle1 = Math.atan2(event.gesturePoints[0].currentPos.y - event.gesturePoints[1].currentPos.y,
9204
+ event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x);
9205
+ var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y,
9206
+ event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x);
9207
+ this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI)));
9208
+ }
8424
9209
  }
8425
9210
  /**
8426
9211
  * Raised when a pinch event occurs on the {@link OpenSeadragon.Viewer#canvas} element.
@@ -8493,97 +9278,64 @@ function onCanvasScroll( event ) {
8493
9278
  return false;
8494
9279
  }
8495
9280
 
8496
- function onContainerExit( event ) {
8497
- if ( !event.insideElementPressed ) {
8498
- THIS[ this.hash ].mouseInside = false;
8499
- if ( !THIS[ this.hash ].animating ) {
8500
- beginControlsAutoHide( this );
8501
- }
8502
- }
9281
+ function onContainerEnter( event ) {
9282
+ THIS[ this.hash ].mouseInside = true;
9283
+ abortControlsAutoHide( this );
8503
9284
  /**
8504
- * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
9285
+ * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
8505
9286
  *
8506
- * @event container-exit
9287
+ * @event container-enter
8507
9288
  * @memberof OpenSeadragon.Viewer
8508
9289
  * @type {object}
8509
9290
  * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8510
9291
  * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
8511
9292
  * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8512
9293
  * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9294
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
8513
9295
  * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8514
9296
  * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
8515
9297
  * @property {Object} originalEvent - The original DOM event.
8516
9298
  * @property {?Object} userData - Arbitrary subscriber-defined object.
8517
9299
  */
8518
- this.raiseEvent( 'container-exit', {
9300
+ this.raiseEvent( 'container-enter', {
8519
9301
  tracker: event.eventSource,
8520
9302
  position: event.position,
8521
9303
  buttons: event.buttons,
9304
+ pointers: event.pointers,
8522
9305
  insideElementPressed: event.insideElementPressed,
8523
9306
  buttonDownAny: event.buttonDownAny,
8524
9307
  originalEvent: event.originalEvent
8525
9308
  });
8526
9309
  }
8527
9310
 
8528
- function onContainerPress( event ) {
8529
- if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) {
8530
- THIS[ this.hash ].mouseInside = true;
8531
- abortControlsAutoHide( this );
8532
- }
8533
- }
8534
-
8535
- function onContainerRelease( event ) {
8536
- if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) {
9311
+ function onContainerExit( event ) {
9312
+ if ( event.pointers < 1 ) {
8537
9313
  THIS[ this.hash ].mouseInside = false;
8538
9314
  if ( !THIS[ this.hash ].animating ) {
8539
9315
  beginControlsAutoHide( this );
8540
9316
  }
8541
9317
  }
8542
9318
  /**
8543
- * Raised when the mouse button is released or touch ends on the {@link OpenSeadragon.Viewer#container} element.
8544
- *
8545
- * @event container-release
8546
- * @memberof OpenSeadragon.Viewer
8547
- * @type {object}
8548
- * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8549
- * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
8550
- * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8551
- * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8552
- * @property {Boolean} insideElementReleased - True if the cursor still inside the tracked element when the button was released.
8553
- * @property {Object} originalEvent - The original DOM event.
8554
- * @property {?Object} userData - Arbitrary subscriber-defined object.
8555
- */
8556
- this.raiseEvent( 'container-release', {
8557
- tracker: event.eventSource,
8558
- position: event.position,
8559
- insideElementPressed: event.insideElementPressed,
8560
- insideElementReleased: event.insideElementReleased,
8561
- originalEvent: event.originalEvent
8562
- });
8563
- }
8564
-
8565
- function onContainerEnter( event ) {
8566
- THIS[ this.hash ].mouseInside = true;
8567
- abortControlsAutoHide( this );
8568
- /**
8569
- * Raised when the cursor enters the {@link OpenSeadragon.Viewer#container} element.
9319
+ * Raised when the cursor leaves the {@link OpenSeadragon.Viewer#container} element.
8570
9320
  *
8571
- * @event container-enter
9321
+ * @event container-exit
8572
9322
  * @memberof OpenSeadragon.Viewer
8573
9323
  * @type {object}
8574
9324
  * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
8575
9325
  * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
8576
9326
  * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
8577
9327
  * @property {Number} buttons - Current buttons pressed. A combination of bit flags 0: none, 1: primary (or touch contact), 2: secondary, 4: aux (often middle), 8: X1 (often back), 16: X2 (often forward), 32: pen eraser.
9328
+ * @property {Number} pointers - Number of pointers (all types) active in the tracked element.
8578
9329
  * @property {Boolean} insideElementPressed - True if the left mouse button is currently being pressed and was initiated inside the tracked element, otherwise false.
8579
9330
  * @property {Boolean} buttonDownAny - Was the button down anywhere in the screen during the event. <span style="color:red;">Deprecated. Use buttons instead.</span>
8580
9331
  * @property {Object} originalEvent - The original DOM event.
8581
9332
  * @property {?Object} userData - Arbitrary subscriber-defined object.
8582
9333
  */
8583
- this.raiseEvent( 'container-enter', {
9334
+ this.raiseEvent( 'container-exit', {
8584
9335
  tracker: event.eventSource,
8585
9336
  position: event.position,
8586
9337
  buttons: event.buttons,
9338
+ pointers: event.pointers,
8587
9339
  insideElementPressed: event.insideElementPressed,
8588
9340
  buttonDownAny: event.buttonDownAny,
8589
9341
  originalEvent: event.originalEvent
@@ -8949,9 +9701,9 @@ function onNext(){
8949
9701
  $.Navigator = function( options ){
8950
9702
 
8951
9703
  var viewer = options.viewer,
9704
+ _this = this,
8952
9705
  viewerSize,
8953
- navigatorSize,
8954
- unneededElement;
9706
+ navigatorSize;
8955
9707
 
8956
9708
  //We may need to create a new element and id if they did not
8957
9709
  //provide the id for the existing element
@@ -8997,6 +9749,7 @@ $.Navigator = function( options ){
8997
9749
  sizeRatio: $.DEFAULT_SETTINGS.navigatorSizeRatio
8998
9750
  }, options, {
8999
9751
  element: this.element,
9752
+ tabIndex: -1, // No keyboard navigation, omit from tab order
9000
9753
  //These need to be overridden to prevent recursion since
9001
9754
  //the navigator is a viewer and a viewer has a navigator
9002
9755
  showNavigator: false,
@@ -9011,6 +9764,8 @@ $.Navigator = function( options ){
9011
9764
 
9012
9765
  options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
9013
9766
 
9767
+ $.setElementTouchActionNone( this.element );
9768
+
9014
9769
  this.borderWidth = 2;
9015
9770
  //At some browser magnification levels the display regions lines up correctly, but at some there appears to
9016
9771
  //be a one pixel gap.
@@ -9058,24 +9813,11 @@ $.Navigator = function( options ){
9058
9813
  style.cursor = 'default';
9059
9814
  }( this.displayRegion.style, this.borderWidth ));
9060
9815
 
9061
-
9062
- this.element.innerTracker = new $.MouseTracker({
9063
- element: this.element,
9064
- dragHandler: $.delegate( this, onCanvasDrag ),
9065
- clickHandler: $.delegate( this, onCanvasClick ),
9066
- releaseHandler: $.delegate( this, onCanvasRelease ),
9067
- scrollHandler: $.delegate( this, onCanvasScroll )
9068
- }).setTracking( true );
9069
-
9070
- /*this.displayRegion.outerTracker = new $.MouseTracker({
9071
- element: this.container,
9072
- clickTimeThreshold: this.clickTimeThreshold,
9073
- clickDistThreshold: this.clickDistThreshold,
9074
- enterHandler: $.delegate( this, onContainerEnter ),
9075
- exitHandler: $.delegate( this, onContainerExit ),
9076
- releaseHandler: $.delegate( this, onContainerRelease )
9077
- }).setTracking( this.mouseNavEnabled ? true : false ); // always tracking*/
9078
-
9816
+ this.displayRegionContainer = $.makeNeutralElement("div");
9817
+ this.displayRegionContainer.id = this.element.id + '-displayregioncontainer';
9818
+ this.displayRegionContainer.className = "displayregioncontainer";
9819
+ this.displayRegionContainer.style.width = "100%";
9820
+ this.displayRegionContainer.style.height = "100%";
9079
9821
 
9080
9822
  viewer.addControl(
9081
9823
  this.element,
@@ -9100,12 +9842,28 @@ $.Navigator = function( options ){
9100
9842
 
9101
9843
  $.Viewer.apply( this, [ options ] );
9102
9844
 
9103
- this.element.getElementsByTagName( 'div' )[0].appendChild( this.displayRegion );
9104
- unneededElement = this.element.getElementsByTagName('textarea')[0];
9105
- if (unneededElement) {
9106
- unneededElement.parentNode.removeChild(unneededElement);
9845
+ this.displayRegionContainer.appendChild(this.displayRegion);
9846
+ this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
9847
+
9848
+ if (options.navigatorRotate)
9849
+ {
9850
+ options.viewer.addHandler("rotate", function (args) {
9851
+ _setTransformRotate(_this.displayRegionContainer, args.degrees);
9852
+ _setTransformRotate(_this.displayRegion, -args.degrees);
9853
+ _this.viewport.setRotation(args.degrees);
9854
+ });
9107
9855
  }
9108
9856
 
9857
+ // Remove the base class' (Viewer's) innerTracker and replace it with our own
9858
+ this.innerTracker.destroy();
9859
+ this.innerTracker = new $.MouseTracker({
9860
+ element: this.element,
9861
+ dragHandler: $.delegate( this, onCanvasDrag ),
9862
+ clickHandler: $.delegate( this, onCanvasClick ),
9863
+ releaseHandler: $.delegate( this, onCanvasRelease ),
9864
+ scrollHandler: $.delegate( this, onCanvasScroll )
9865
+ });
9866
+
9109
9867
  };
9110
9868
 
9111
9869
  $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /** @lends OpenSeadragon.Navigator.prototype */{
@@ -9197,12 +9955,9 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
9197
9955
  open: function( source ) {
9198
9956
  this.updateSize();
9199
9957
  var containerSize = this.viewer.viewport.containerSize.times( this.sizeRatio );
9200
- if( source.tileSize > containerSize.x ||
9201
- source.tileSize > containerSize.y ){
9202
- this.minPixelRatio = Math.min(
9203
- containerSize.x,
9204
- containerSize.y
9205
- ) / source.tileSize;
9958
+ var ts = source.getTileSize(source.maxLevel);
9959
+ if ( ts > containerSize.x || ts > containerSize.y ) {
9960
+ this.minPixelRatio = Math.min( containerSize.x, containerSize.y ) / ts;
9206
9961
  } else {
9207
9962
  this.minPixelRatio = this.viewer.minPixelRatio;
9208
9963
  }
@@ -9217,17 +9972,9 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
9217
9972
  * @function
9218
9973
  */
9219
9974
  function onCanvasClick( event ) {
9220
- var newBounds,
9221
- viewerPosition,
9222
- dimensions;
9223
- if (! this.drag) {
9224
- if ( this.viewer.viewport ) {
9225
- this.viewer.viewport.panTo( this.viewport.pointFromPixel( event.position ) );
9226
- this.viewer.viewport.applyConstraints();
9227
- }
9228
- }
9229
- else {
9230
- this.drag = false;
9975
+ if ( event.quick && this.viewer.viewport ) {
9976
+ this.viewer.viewport.panTo( this.viewport.pointFromPixel( event.position ).rotate( -this.viewer.viewport.degrees, this.viewer.viewport.getHomeBounds().getCenter() ) );
9977
+ this.viewer.viewport.applyConstraints();
9231
9978
  }
9232
9979
  }
9233
9980
 
@@ -9238,7 +9985,6 @@ function onCanvasClick( event ) {
9238
9985
  */
9239
9986
  function onCanvasDrag( event ) {
9240
9987
  if ( this.viewer.viewport ) {
9241
- this.drag = true;
9242
9988
  if( !this.panHorizontal ){
9243
9989
  event.delta.x = 0;
9244
9990
  }
@@ -9299,6 +10045,19 @@ function onCanvasScroll( event ) {
9299
10045
  return false;
9300
10046
  }
9301
10047
 
10048
+ /**
10049
+ * @function
10050
+ * @private
10051
+ * @param {Object} element
10052
+ * @param {Number} degrees
10053
+ */
10054
+ function _setTransformRotate (element, degrees) {
10055
+ element.style.webkitTransform = "rotate(" + degrees + "deg)";
10056
+ element.style.mozTransform = "rotate(" + degrees + "deg)";
10057
+ element.style.msTransform = "rotate(" + degrees + "deg)";
10058
+ element.style.oTransform = "rotate(" + degrees + "deg)";
10059
+ element.style.transform = "rotate(" + degrees + "deg)";
10060
+ }
9302
10061
 
9303
10062
  }( OpenSeadragon ));
9304
10063
 
@@ -9749,6 +10508,8 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
9749
10508
  */
9750
10509
  /**
9751
10510
  * The size of the image tiles used to compose the image.
10511
+ * Please note that tileSize may be deprecated in a future release.
10512
+ * Instead the getTileSize(level) function should be used.
9752
10513
  * @member {Number} tileSize
9753
10514
  * @memberof OpenSeadragon.TileSource#
9754
10515
  */
@@ -9816,6 +10577,18 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
9816
10577
 
9817
10578
  $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9818
10579
 
10580
+ /**
10581
+ * Return the tileSize for a given level.
10582
+ * Subclasses should override this if tileSizes can be different at different levels
10583
+ * such as in IIIFTileSource. Code should use this function rather than reading
10584
+ * from .tileSize directly. tileSize may be deprecated in a future release.
10585
+ * @function
10586
+ * @param {Number} level
10587
+ */
10588
+ getTileSize: function( level ) {
10589
+ return this.tileSize;
10590
+ },
10591
+
9819
10592
  /**
9820
10593
  * @function
9821
10594
  * @param {Number} level
@@ -9842,8 +10615,8 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9842
10615
  */
9843
10616
  getNumTiles: function( level ) {
9844
10617
  var scale = this.getLevelScale( level ),
9845
- x = Math.ceil( scale * this.dimensions.x / this.tileSize ),
9846
- y = Math.ceil( scale * this.dimensions.y / this.tileSize );
10618
+ x = Math.ceil( scale * this.dimensions.x / this.getTileSize(level) ),
10619
+ y = Math.ceil( scale * this.dimensions.y / this.getTileSize(level) );
9847
10620
 
9848
10621
  return new $.Point( x, y );
9849
10622
  },
@@ -9867,10 +10640,11 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9867
10640
  */
9868
10641
  getClosestLevel: function( rect ) {
9869
10642
  var i,
9870
- tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.tileSize ),
10643
+ tilesPerSide,
9871
10644
  tiles;
9872
10645
  for( i = this.minLevel; i < this.maxLevel; i++ ){
9873
10646
  tiles = this.getNumTiles( i );
10647
+ tilesPerSide = Math.floor( Math.max( rect.x, rect.y ) / this.getTileSize(i) );
9874
10648
  if( Math.max( tiles.x, tiles.y ) + 1 >= tilesPerSide ){
9875
10649
  break;
9876
10650
  }
@@ -9885,8 +10659,8 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9885
10659
  */
9886
10660
  getTileAtPoint: function( level, point ) {
9887
10661
  var pixel = point.times( this.dimensions.x ).times( this.getLevelScale(level ) ),
9888
- tx = Math.floor( pixel.x / this.tileSize ),
9889
- ty = Math.floor( pixel.y / this.tileSize );
10662
+ tx = Math.floor( pixel.x / this.getTileSize(level) ),
10663
+ ty = Math.floor( pixel.y / this.getTileSize(level) );
9890
10664
 
9891
10665
  return new $.Point( tx, ty );
9892
10666
  },
@@ -9899,10 +10673,11 @@ $.TileSource.prototype = /** @lends OpenSeadragon.TileSource.prototype */{
9899
10673
  */
9900
10674
  getTileBounds: function( level, x, y ) {
9901
10675
  var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
9902
- px = ( x === 0 ) ? 0 : this.tileSize * x - this.tileOverlap,
9903
- py = ( y === 0 ) ? 0 : this.tileSize * y - this.tileOverlap,
9904
- sx = this.tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
9905
- sy = this.tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
10676
+ tileSize = this.getTileSize(level),
10677
+ px = ( x === 0 ) ? 0 : tileSize * x - this.tileOverlap,
10678
+ py = ( y === 0 ) ? 0 : tileSize * y - this.tileOverlap,
10679
+ sx = tileSize + ( x === 0 ? 1 : 2 ) * this.tileOverlap,
10680
+ sy = tileSize + ( y === 0 ? 1 : 2 ) * this.tileOverlap,
9906
10681
  scale = 1.0 / dimensionsScaled.x;
9907
10682
 
9908
10683
  sx = Math.min( sx, dimensionsScaled.x - px );
@@ -10287,8 +11062,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
10287
11062
  var ns;
10288
11063
  if ( data.Image ) {
10289
11064
  ns = data.Image.xmlns;
10290
- } else if ( data.documentElement && "Image" == data.documentElement.tagName ) {
10291
- ns = data.documentElement.namespaceURI;
11065
+ } else if ( data.documentElement) {
11066
+ if ("Image" == data.documentElement.localName || "Image" == data.documentElement.tagName) {
11067
+ ns = data.documentElement.namespaceURI;
11068
+ }
10292
11069
  }
10293
11070
 
10294
11071
  return ( "http://schemas.microsoft.com/deepzoom/2008" == ns ||
@@ -10318,7 +11095,12 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
10318
11095
 
10319
11096
  if (url && !options.tilesUrl) {
10320
11097
  options.tilesUrl = url.replace(/([^\/]+)\.(dzi|xml|js)(\?.*|$)/, '$1_files/');
10321
- options.queryParams = url.match(/\?.*/);
11098
+
11099
+ if (url.search(/\.(dzi|xml|js)\?/) != -1) {
11100
+ options.queryParams = url.match(/\?.*/);
11101
+ }else{
11102
+ options.queryParams = '';
11103
+ }
10322
11104
  }
10323
11105
 
10324
11106
  return options;
@@ -10396,7 +11178,8 @@ function configureFromXML( tileSource, xmlDoc ){
10396
11178
  }
10397
11179
 
10398
11180
  var root = xmlDoc.documentElement,
10399
- rootName = root.tagName,
11181
+ rootName = root.localName || root.tagName,
11182
+ ns = xmlDoc.documentElement.namespaceURI,
10400
11183
  configuration = null,
10401
11184
  displayRects = [],
10402
11185
  dispRectNodes,
@@ -10408,7 +11191,11 @@ function configureFromXML( tileSource, xmlDoc ){
10408
11191
  if ( rootName == "Image" ) {
10409
11192
 
10410
11193
  try {
10411
- sizeNode = root.getElementsByTagName( "Size" )[ 0 ];
11194
+ sizeNode = root.getElementsByTagName("Size" )[ 0 ];
11195
+ if (sizeNode === undefined) {
11196
+ sizeNode = root.getElementsByTagNameNS(ns, "Size" )[ 0 ];
11197
+ }
11198
+
10412
11199
  configuration = {
10413
11200
  Image: {
10414
11201
  xmlns: "http://schemas.microsoft.com/deepzoom/2008",
@@ -10430,10 +11217,17 @@ function configureFromXML( tileSource, xmlDoc ){
10430
11217
  );
10431
11218
  }
10432
11219
 
10433
- dispRectNodes = root.getElementsByTagName( "DisplayRect" );
11220
+ dispRectNodes = root.getElementsByTagName("DisplayRect" );
11221
+ if (dispRectNodes === undefined) {
11222
+ dispRectNodes = root.getElementsByTagNameNS(ns, "DisplayRect" )[ 0 ];
11223
+ }
11224
+
10434
11225
  for ( i = 0; i < dispRectNodes.length; i++ ) {
10435
11226
  dispRectNode = dispRectNodes[ i ];
10436
- rectNode = dispRectNode.getElementsByTagName( "Rect" )[ 0 ];
11227
+ rectNode = dispRectNode.getElementsByTagName("Rect" )[ 0 ];
11228
+ if (rectNode === undefined) {
11229
+ rectNode = dispRectNode.getElementsByTagNameNS(ns, "Rect" )[ 0 ];
11230
+ }
10437
11231
 
10438
11232
  displayRects.push({
10439
11233
  Rect: {
@@ -10509,357 +11303,27 @@ function configureFromObject( tileSource, configuration ){
10509
11303
  parseInt( rectData.Height, 10 ),
10510
11304
  parseInt( rectData.MinLevel, 10 ),
10511
11305
  parseInt( rectData.MaxLevel, 10 )
10512
- ));
10513
- }
10514
-
10515
- return $.extend(true, {
10516
- width: width, /* width *required */
10517
- height: height, /* height *required */
10518
- tileSize: tileSize, /* tileSize *required */
10519
- tileOverlap: tileOverlap, /* tileOverlap *required */
10520
- minLevel: null, /* minLevel */
10521
- maxLevel: null, /* maxLevel */
10522
- tilesUrl: tilesUrl, /* tilesUrl */
10523
- fileFormat: fileFormat, /* fileFormat */
10524
- displayRects: displayRects /* displayRects */
10525
- }, configuration );
10526
-
10527
- }
10528
-
10529
- }( OpenSeadragon ));
10530
-
10531
- /*
10532
- * OpenSeadragon - IIIFTileSource
10533
- *
10534
- * Copyright (C) 2009 CodePlex Foundation
10535
- * Copyright (C) 2010-2013 OpenSeadragon contributors
10536
- *
10537
- * Redistribution and use in source and binary forms, with or without
10538
- * modification, are permitted provided that the following conditions are
10539
- * met:
10540
- *
10541
- * - Redistributions of source code must retain the above copyright notice,
10542
- * this list of conditions and the following disclaimer.
10543
- *
10544
- * - Redistributions in binary form must reproduce the above copyright
10545
- * notice, this list of conditions and the following disclaimer in the
10546
- * documentation and/or other materials provided with the distribution.
10547
- *
10548
- * - Neither the name of CodePlex Foundation nor the names of its
10549
- * contributors may be used to endorse or promote products derived from
10550
- * this software without specific prior written permission.
10551
- *
10552
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
10553
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
10554
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
10555
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
10556
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
10557
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
10558
- * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
10559
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
10560
- * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
10561
- * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
10562
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
10563
- */
10564
-
10565
- /*
10566
- * The getTileUrl implementation is based on Jon Stroop's Python version,
10567
- * which is released under the New BSD license:
10568
- * https://gist.github.com/jpstroop/4624253
10569
- */
10570
-
10571
-
10572
- (function( $ ){
10573
-
10574
- /**
10575
- * @class IIIFTileSource
10576
- * @classdesc A client implementation of the International Image Interoperability
10577
- * Format: Image API Draft 0.2
10578
- *
10579
- * @memberof OpenSeadragon
10580
- * @extends OpenSeadragon.TileSource
10581
- * @see http://library.stanford.edu/iiif/image-api/
10582
- */
10583
- $.IIIFTileSource = function( options ){
10584
-
10585
- $.extend( true, this, options );
10586
-
10587
- if( !(this.height && this.width && this.identifier && this.tilesUrl ) ){
10588
- throw new Error('IIIF required parameters not provided.');
10589
- }
10590
-
10591
- //TODO: at this point the base tile source implementation assumes
10592
- // a tile is a square and so only has one property tileSize
10593
- // to store it. It may be possible to make tileSize a vector
10594
- // OpenSeadraon.Point but would require careful implementation
10595
- // to preserve backward compatibility.
10596
- options.tileSize = this.tile_width;
10597
-
10598
- if (! options.maxLevel ) {
10599
- var mf = -1;
10600
- var scfs = this.scale_factors || this.scale_factor;
10601
- if ( scfs instanceof Array ) {
10602
- for ( var i = 0; i < scfs.length; i++ ) {
10603
- var cf = Number( scfs[i] );
10604
- if ( !isNaN( cf ) && cf > mf ) { mf = cf; }
10605
- }
10606
- }
10607
- if ( mf < 0 ) { options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2))); }
10608
- else { options.maxLevel = mf; }
10609
- }
10610
-
10611
- $.TileSource.apply( this, [ options ] );
10612
- };
10613
-
10614
- $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{
10615
- /**
10616
- * Determine if the data and/or url imply the image service is supported by
10617
- * this tile source.
10618
- * @method
10619
- * @param {Object|Array} data
10620
- * @param {String} optional - url
10621
- */
10622
- supports: function( data, url ){
10623
- return (
10624
- data.ns &&
10625
- "http://library.stanford.edu/iiif/image-api/ns/" == data.ns
10626
- ) || (
10627
- data.profile && (
10628
- "http://library.stanford.edu/iiif/image-api/compliance.html#level1" == data.profile ||
10629
- "http://library.stanford.edu/iiif/image-api/compliance.html#level2" == data.profile ||
10630
- "http://library.stanford.edu/iiif/image-api/compliance.html#level3" == data.profile ||
10631
- "http://library.stanford.edu/iiif/image-api/compliance.html" == data.profile
10632
- )
10633
- ) || (
10634
- data.documentElement &&
10635
- "info" == data.documentElement.tagName &&
10636
- "http://library.stanford.edu/iiif/image-api/ns/" ==
10637
- data.documentElement.namespaceURI
10638
- );
10639
- },
10640
-
10641
- /**
10642
- *
10643
- * @method
10644
- * @param {Object|XMLDocument} data - the raw configuration
10645
- * @param {String} url - the url the data was retreived from if any.
10646
- * @return {Object} options - A dictionary of keyword arguments sufficient
10647
- * to configure this tile source via its constructor.
10648
- */
10649
- configure: function( data, url ){
10650
- var service,
10651
- options,
10652
- host;
10653
-
10654
- if( !$.isPlainObject(data) ){
10655
-
10656
- options = configureFromXml( this, data );
10657
-
10658
- }else{
10659
-
10660
- options = configureFromObject( this, data );
10661
- }
10662
-
10663
- if( url && !options.tilesUrl ){
10664
- service = url.split('/');
10665
- service.pop(); //info.json or info.xml
10666
- service = service.join('/');
10667
- if( 'http' !== url.substring( 0, 4 ) ){
10668
- host = location.protocol + '//' + location.host;
10669
- service = host + service;
10670
- }
10671
- options.tilesUrl = service.replace(
10672
- data.identifier,
10673
- ''
10674
- );
10675
- }
10676
-
10677
- return options;
10678
- },
10679
-
10680
- /**
10681
- * Responsible for retreiving the url which will return an image for the
10682
- * region speified by the given x, y, and level components.
10683
- * @method
10684
- * @param {Number} level - z index
10685
- * @param {Number} x
10686
- * @param {Number} y
10687
- * @throws {Error}
10688
- */
10689
- getTileUrl: function( level, x, y ){
10690
-
10691
- //# constants
10692
- var IIIF_ROTATION = '0',
10693
- IIIF_QUALITY = 'native.jpg',
10694
-
10695
- //## get the scale (level as a decimal)
10696
- scale = Math.pow( 0.5, this.maxLevel - level ),
10697
-
10698
- //## get iiif size
10699
- // iiif_size = 'pct:' + ( scale * 100 ),
10700
-
10701
- //# image dimensions at this level
10702
- level_width = Math.ceil( this.width * scale ),
10703
- level_height = Math.ceil( this.height * scale ),
10704
-
10705
- //## iiif region
10706
- iiif_tile_size_width = Math.ceil( this.tileSize / scale ),
10707
- iiif_tile_size_height = Math.ceil( this.tileSize / scale ),
10708
- iiif_region,
10709
- iiif_tile_x,
10710
- iiif_tile_y,
10711
- iiif_tile_w,
10712
- iiif_tile_h,
10713
- iiif_size;
10714
-
10715
-
10716
- if ( level_width < this.tile_width && level_height < this.tile_height ){
10717
- iiif_size = level_width + ","; // + level_height; only one dim. for IIIF level 1 compliance
10718
- iiif_region = 'full';
10719
- } else {
10720
- iiif_tile_x = x * iiif_tile_size_width;
10721
- iiif_tile_y = y * iiif_tile_size_height;
10722
- iiif_tile_w = Math.min( iiif_tile_size_width, this.width - iiif_tile_x );
10723
- iiif_tile_h = Math.min( iiif_tile_size_height, this.height - iiif_tile_y );
10724
- iiif_size = Math.ceil(iiif_tile_w * scale) + ",";
10725
- iiif_region = [ iiif_tile_x, iiif_tile_y, iiif_tile_w, iiif_tile_h ].join(',');
10726
- }
10727
-
10728
- return [
10729
- this.tilesUrl,
10730
- this.identifier,
10731
- iiif_region,
10732
- iiif_size,
10733
- IIIF_ROTATION,
10734
- IIIF_QUALITY
10735
- ].join('/');
10736
- }
10737
-
10738
-
10739
- });
10740
-
10741
- /**
10742
- * @private
10743
- * @inner
10744
- * @function
10745
- * @example
10746
- * <?xml version="1.0" encoding="UTF-8"?>
10747
- * <info xmlns="http://library.stanford.edu/iiif/image-api/ns/">
10748
- * <identifier>1E34750D-38DB-4825-A38A-B60A345E591C</identifier>
10749
- * <width>6000</width>
10750
- * <height>4000</height>
10751
- * <scale_factors>
10752
- * <scale_factor>1</scale_factor>
10753
- * <scale_factor>2</scale_factor>
10754
- * <scale_factor>4</scale_factor>
10755
- * </scale_factors>
10756
- * <tile_width>1024</tile_width>
10757
- * <tile_height>1024</tile_height>
10758
- * <formats>
10759
- * <format>jpg</format>
10760
- * <format>png</format>
10761
- * </formats>
10762
- * <qualities>
10763
- * <quality>native</quality>
10764
- * <quality>grey</quality>
10765
- * </qualities>
10766
- * </info>
10767
- */
10768
- function configureFromXml( tileSource, xmlDoc ){
10769
-
10770
- //parse the xml
10771
- if ( !xmlDoc || !xmlDoc.documentElement ) {
10772
- throw new Error( $.getString( "Errors.Xml" ) );
10773
- }
10774
-
10775
- var root = xmlDoc.documentElement,
10776
- rootName = root.tagName,
10777
- configuration = null;
10778
-
10779
- if ( rootName == "info" ) {
10780
-
10781
- try {
10782
-
10783
- configuration = {
10784
- "ns": root.namespaceURI
10785
- };
10786
-
10787
- parseXML( root, configuration );
10788
-
10789
- return configureFromObject( tileSource, configuration );
10790
-
10791
- } catch ( e ) {
10792
- throw (e instanceof Error) ?
10793
- e :
10794
- new Error( $.getString("Errors.IIIF") );
10795
- }
10796
- }
10797
-
10798
- throw new Error( $.getString( "Errors.IIIF" ) );
10799
-
10800
- }
10801
-
10802
-
10803
- /**
10804
- * @private
10805
- * @inner
10806
- * @function
10807
- */
10808
- function parseXML( node, configuration, property ){
10809
- var i,
10810
- value;
10811
- if( node.nodeType == 3 && property ){//text node
10812
- value = node.nodeValue.trim();
10813
- if( value.match(/^\d*$/)){
10814
- value = Number( value );
10815
- }
10816
- if( !configuration[ property ] ){
10817
- configuration[ property ] = value;
10818
- }else{
10819
- if( !$.isArray( configuration[ property ] ) ){
10820
- configuration[ property ] = [ configuration[ property ] ];
10821
- }
10822
- configuration[ property ].push( value );
10823
- }
10824
- } else if( node.nodeType == 1 ){
10825
- for( i = 0; i < node.childNodes.length; i++ ){
10826
- parseXML( node.childNodes[ i ], configuration, node.nodeName );
10827
- }
10828
- }
10829
- }
10830
-
10831
-
10832
- /**
10833
- * @private
10834
- * @inner
10835
- * @function
10836
- * @example
10837
- * {
10838
- * "profile" : "http://library.stanford.edu/iiif/image-api/compliance.html#level1",
10839
- * "identifier" : "1E34750D-38DB-4825-A38A-B60A345E591C",
10840
- * "width" : 6000,
10841
- * "height" : 4000,
10842
- * "scale_factors" : [ 1, 2, 4 ],
10843
- * "tile_width" : 1024,
10844
- * "tile_height" : 1024,
10845
- * "formats" : [ "jpg", "png" ],
10846
- * "quality" : [ "native", "grey" ]
10847
- * }
10848
- */
10849
- function configureFromObject( tileSource, configuration ){
10850
- //the image_host property is not part of the iiif standard but is included here to
10851
- //allow the info.json and info.xml specify a different server to load the
10852
- //images from so we can test the implementation.
10853
- if( configuration.image_host ){
10854
- configuration.tilesUrl = configuration.image_host;
11306
+ ));
10855
11307
  }
10856
- return configuration;
11308
+
11309
+ return $.extend(true, {
11310
+ width: width, /* width *required */
11311
+ height: height, /* height *required */
11312
+ tileSize: tileSize, /* tileSize *required */
11313
+ tileOverlap: tileOverlap, /* tileOverlap *required */
11314
+ minLevel: null, /* minLevel */
11315
+ maxLevel: null, /* maxLevel */
11316
+ tilesUrl: tilesUrl, /* tilesUrl */
11317
+ fileFormat: fileFormat, /* fileFormat */
11318
+ displayRects: displayRects /* displayRects */
11319
+ }, configuration );
11320
+
10857
11321
  }
10858
11322
 
10859
11323
  }( OpenSeadragon ));
10860
11324
 
10861
11325
  /*
10862
- * OpenSeadragon - IIIF1_1TileSource
11326
+ * OpenSeadragon - IIIFTileSource
10863
11327
  *
10864
11328
  * Copyright (C) 2009 CodePlex Foundation
10865
11329
  * Copyright (C) 2010-2013 OpenSeadragon contributors
@@ -10895,47 +11359,58 @@ function configureFromObject( tileSource, configuration ){
10895
11359
  (function( $ ){
10896
11360
 
10897
11361
  /**
10898
- * @class IIIF1_1TileSource
11362
+ * @class IIIFTileSource
10899
11363
  * @classdesc A client implementation of the International Image Interoperability
10900
- * Format: Image API 1.1
11364
+ * Format: Image API 1.0 - 2.0
10901
11365
  *
10902
11366
  * @memberof OpenSeadragon
10903
11367
  * @extends OpenSeadragon.TileSource
10904
- * @see http://library.stanford.edu/iiif/image-api/
11368
+ * @see http://iiif.io/api/image/
10905
11369
  */
10906
- $.IIIF1_1TileSource = function( options ){
11370
+ $.IIIFTileSource = function( options ){
10907
11371
 
10908
11372
 
10909
11373
  $.extend( true, this, options );
10910
11374
 
10911
-
10912
- if ( !( this.height && this.width && this['@id'] ) ){
11375
+ if ( !( this.height && this.width && this['@id'] ) ) {
10913
11376
  throw new Error( 'IIIF required parameters not provided.' );
10914
11377
  }
10915
11378
 
10916
- if ( ( this.profile &&
10917
- this.profile == "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0" ) ){
10918
- // what if not reporting a profile?
10919
- throw new Error( 'IIIF Image API 1.1 compliance level 1 or greater is required.' );
10920
- }
11379
+ options.tileSizePerScaleFactor = {};
10921
11380
 
11381
+ // N.B. 2.0 renamed scale_factors to scaleFactors
10922
11382
  if ( this.tile_width ) {
10923
11383
  options.tileSize = this.tile_width;
10924
11384
  } else if ( this.tile_height ) {
10925
11385
  options.tileSize = this.tile_height;
11386
+ } else if ( this.tiles ) {
11387
+ // Version 2.0 forwards
11388
+ if ( this.tiles.length == 1 ) {
11389
+ options.tileSize = this.tiles[0].width;
11390
+ this.scale_factors = this.tiles[0].scaleFactors;
11391
+ } else {
11392
+ // Multiple tile sizes at different levels
11393
+ this.scale_factors = [];
11394
+ for (var t = 0; t < this.tiles.length; t++ ) {
11395
+ for (var sf = 0; sf < this.tiles[t].scaleFactors.length; sf++) {
11396
+ var scaleFactor = this.tiles[t].scaleFactors[sf];
11397
+ this.scale_factors.push(scaleFactor);
11398
+ options.tileSizePerScaleFactor[scaleFactor] = this.tiles[t].width;
11399
+ }
11400
+ }
11401
+ }
10926
11402
  } else {
10927
- // use the largest of tileOptions that is smaller than the short
10928
- // dimension
11403
+ // use the largest of tileOptions that is smaller than the short dimension
10929
11404
 
10930
11405
  var shortDim = Math.min( this.height, this.width ),
10931
11406
  tileOptions = [256,512,1024],
10932
11407
  smallerTiles = [];
10933
11408
 
10934
- for ( var c = 0; c < tileOptions.length; c++ ) {
10935
- if ( tileOptions[c] <= shortDim ) {
10936
- smallerTiles.push( tileOptions[c] );
10937
- }
11409
+ for ( var c = 0; c < tileOptions.length; c++ ) {
11410
+ if ( tileOptions[c] <= shortDim ) {
11411
+ smallerTiles.push( tileOptions[c] );
10938
11412
  }
11413
+ }
10939
11414
 
10940
11415
  if ( smallerTiles.length > 0 ) {
10941
11416
  options.tileSize = Math.max.apply( null, smallerTiles );
@@ -10948,22 +11423,17 @@ $.IIIF1_1TileSource = function( options ){
10948
11423
  }
10949
11424
 
10950
11425
  if ( !options.maxLevel ) {
10951
- var mf = -1;
10952
- var scfs = this.scale_factors || this.scale_factor;
10953
- if ( scfs instanceof Array ) {
10954
- for ( var i = 0; i < scfs.length; i++ ) {
10955
- var cf = Number( scfs[i] );
10956
- if ( !isNaN( cf ) && cf > mf ) { mf = cf; }
10957
- }
11426
+ if ( !this.scale_factors ) {
11427
+ options.maxLevel = Number( Math.ceil( Math.log( Math.max( this.width, this.height ), 2 ) ) );
11428
+ } else {
11429
+ options.maxLevel = Math.floor( Math.pow( Math.max.apply(null, this.scale_factors), 0.5) );
10958
11430
  }
10959
- if ( mf < 0 ) { options.maxLevel = Number( Math.ceil( Math.log( Math.max( this.width, this.height ), 2 ) ) ); }
10960
- else { options.maxLevel = mf; }
10961
11431
  }
10962
11432
 
10963
11433
  $.TileSource.apply( this, [ options ] );
10964
11434
  };
10965
11435
 
10966
- $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIF1_1TileSource.prototype */{
11436
+ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSeadragon.IIIFTileSource.prototype */{
10967
11437
  /**
10968
11438
  * Determine if the data and/or url imply the image service is supported by
10969
11439
  * this tile source.
@@ -10972,15 +11442,39 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
10972
11442
  * @param {String} optional - url
10973
11443
  */
10974
11444
  supports: function( data, url ) {
10975
- return ( data['@context'] &&
10976
- data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" );
11445
+ // Version 2.0 and forwards
11446
+ if (data.protocol && data.protocol == 'http://iiif.io/api/image') {
11447
+ return true;
11448
+ // Version 1.1
11449
+ } else if ( data['@context'] && (
11450
+ data['@context'] == "http://library.stanford.edu/iiif/image-api/1.1/context.json" ||
11451
+ data['@context'] == "http://iiif.io/api/image/1/context.json") ) {
11452
+ // N.B. the iiif.io context is wrong, but where the representation lives so likely to be used
11453
+ return true;
11454
+
11455
+ // Version 1.0
11456
+ } else if ( data.profile &&
11457
+ data.profile.indexOf("http://library.stanford.edu/iiif/image-api/compliance.html") === 0) {
11458
+ return true;
11459
+ } else if ( data.identifier && data.width && data.height ) {
11460
+ return true;
11461
+ } else if ( data.documentElement &&
11462
+ "info" == data.documentElement.tagName &&
11463
+ "http://library.stanford.edu/iiif/image-api/ns/" ==
11464
+ data.documentElement.namespaceURI) {
11465
+ return true;
11466
+
11467
+ // Not IIIF
11468
+ } else {
11469
+ return false;
11470
+ }
10977
11471
  },
10978
11472
 
10979
11473
  /**
10980
11474
  *
10981
11475
  * @function
10982
11476
  * @param {Object} data - the raw configuration
10983
- * @example <caption>IIIF 1.1 Info Looks like this (XML syntax is no more)</caption>
11477
+ * @example <caption>IIIF 1.1 Info Looks like this</caption>
10984
11478
  * {
10985
11479
  * "@context" : "http://library.stanford.edu/iiif/image-api/1.1/context.json",
10986
11480
  * "@id" : "http://iiif.example.com/prefix/1E34750D-38DB-4825-A38A-B60A345E591C",
@@ -10994,9 +11488,37 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
10994
11488
  * "profile" : "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0"
10995
11489
  * }
10996
11490
  */
10997
- configure: function( data ){
10998
- return data;
11491
+ configure: function( data, url ){
11492
+ // Try to deduce our version and fake it upwards if needed
11493
+ if ( !$.isPlainObject(data) ) {
11494
+ var options = configureFromXml10( data );
11495
+ options['@context'] = "http://iiif.io/api/image/1.0/context.json";
11496
+ options['@id'] = url.replace('/info.xml', '');
11497
+ return options;
11498
+ } else if ( !data['@context'] ) {
11499
+ data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
11500
+ data['@id'] = url.replace('/info.json', '');
11501
+ return data;
11502
+ } else {
11503
+ return data;
11504
+ }
11505
+ },
11506
+
11507
+ /**
11508
+ * Return the tileSize for the given level.
11509
+ * @function
11510
+ * @param {Number} level
11511
+ */
11512
+
11513
+ getTileSize: function( level ){
11514
+ var scaleFactor = Math.pow(2, this.maxLevel - level);
11515
+ // cache it in case any external code is going to read it directly
11516
+ if (this.tileSizePerScaleFactor && this.tileSizePerScaleFactor[scaleFactor]) {
11517
+ this.tileSize = this.tileSizePerScaleFactor[scaleFactor];
11518
+ }
11519
+ return this.tileSize;
10999
11520
  },
11521
+
11000
11522
  /**
11001
11523
  * Responsible for retreiving the url which will return an image for the
11002
11524
  * region specified by the given x, y, and level components.
@@ -11009,10 +11531,7 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
11009
11531
  getTileUrl: function( level, x, y ){
11010
11532
 
11011
11533
  //# constants
11012
-
11013
11534
  var IIIF_ROTATION = '0',
11014
- IIIF_QUALITY = 'native.jpg',
11015
-
11016
11535
  //## get the scale (level as a decimal)
11017
11536
  scale = Math.pow( 0.5, this.maxLevel - level ),
11018
11537
 
@@ -11021,16 +11540,28 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
11021
11540
  levelHeight = Math.ceil( this.height * scale ),
11022
11541
 
11023
11542
  //## iiif region
11024
- iiifTileSizeWidth = Math.ceil( this.tileSize / scale ),
11025
- iiifTileSizeHeight = Math.ceil( this.tileSize / scale ),
11543
+ iiifTileSizeWidth,
11544
+ iiifTileSizeHeight,
11026
11545
  iiifRegion,
11027
11546
  iiifTileX,
11028
11547
  iiifTileY,
11029
11548
  iiifTileW,
11030
11549
  iiifTileH,
11031
11550
  iiifSize,
11551
+ iiifQuality,
11032
11552
  uri;
11033
11553
 
11554
+ iiifTileSizeWidth = Math.ceil( this.getTileSize(level) / scale );
11555
+ iiifTileSizeHeight = iiifTileSizeWidth;
11556
+
11557
+ if ( this['@context'].indexOf('/1.0/context.json') > -1 ||
11558
+ this['@context'].indexOf('/1.1/context.json') > -1 ||
11559
+ this['@context'].indexOf('/1/context.json') > -1 ) {
11560
+ iiifQuality = "native.jpg";
11561
+ } else {
11562
+ iiifQuality = "default.jpg";
11563
+ }
11564
+
11034
11565
  if ( levelWidth < this.tile_width && levelHeight < this.tile_height ){
11035
11566
  iiifSize = levelWidth + ",";
11036
11567
  iiifRegion = 'full';
@@ -11039,16 +11570,65 @@ $.extend( $.IIIF1_1TileSource.prototype, $.TileSource.prototype, /** @lends Open
11039
11570
  iiifTileY = y * iiifTileSizeHeight;
11040
11571
  iiifTileW = Math.min( iiifTileSizeWidth, this.width - iiifTileX );
11041
11572
  iiifTileH = Math.min( iiifTileSizeHeight, this.height - iiifTileY );
11042
-
11043
11573
  iiifSize = Math.ceil( iiifTileW * scale ) + ",";
11044
-
11045
11574
  iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
11046
11575
  }
11047
- uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, IIIF_QUALITY ].join( '/' );
11576
+ uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' );
11577
+
11048
11578
  return uri;
11049
11579
  }
11580
+
11050
11581
  });
11051
11582
 
11583
+ function configureFromXml10(xmlDoc) {
11584
+ //parse the xml
11585
+ if ( !xmlDoc || !xmlDoc.documentElement ) {
11586
+ throw new Error( $.getString( "Errors.Xml" ) );
11587
+ }
11588
+
11589
+ var root = xmlDoc.documentElement,
11590
+ rootName = root.tagName,
11591
+ configuration = null;
11592
+
11593
+ if ( rootName == "info" ) {
11594
+ try {
11595
+ configuration = {};
11596
+ parseXML10( root, configuration );
11597
+ return configuration;
11598
+
11599
+ } catch ( e ) {
11600
+ throw (e instanceof Error) ?
11601
+ e :
11602
+ new Error( $.getString("Errors.IIIF") );
11603
+ }
11604
+ }
11605
+ throw new Error( $.getString( "Errors.IIIF" ) );
11606
+ }
11607
+
11608
+ function parseXML10( node, configuration, property ) {
11609
+ var i,
11610
+ value;
11611
+ if ( node.nodeType == 3 && property ) {//text node
11612
+ value = node.nodeValue.trim();
11613
+ if( value.match(/^\d*$/)){
11614
+ value = Number( value );
11615
+ }
11616
+ if( !configuration[ property ] ){
11617
+ configuration[ property ] = value;
11618
+ }else{
11619
+ if( !$.isArray( configuration[ property ] ) ){
11620
+ configuration[ property ] = [ configuration[ property ] ];
11621
+ }
11622
+ configuration[ property ].push( value );
11623
+ }
11624
+ } else if( node.nodeType == 1 ){
11625
+ for( i = 0; i < node.childNodes.length; i++ ){
11626
+ parseXML10( node.childNodes[ i ], configuration, node.nodeName );
11627
+ }
11628
+ }
11629
+ }
11630
+
11631
+
11052
11632
  }( OpenSeadragon ));
11053
11633
 
11054
11634
  /*
@@ -11924,6 +12504,7 @@ $.Button = function( options ) {
11924
12504
  this.tooltip;
11925
12505
 
11926
12506
  this.element.style.position = "relative";
12507
+ $.setElementTouchActionNone( this.element );
11927
12508
 
11928
12509
  this.imgGroup.style.position =
11929
12510
  this.imgHover.style.position =
@@ -12141,7 +12722,7 @@ $.Button = function( options ) {
12141
12722
  return true;
12142
12723
  }
12143
12724
 
12144
- }).setTracking( true );
12725
+ });
12145
12726
 
12146
12727
  outTo( this, $.ButtonState.REST );
12147
12728
  };
@@ -12375,6 +12956,8 @@ $.ButtonGroup = function( options ) {
12375
12956
  }
12376
12957
  }
12377
12958
 
12959
+ $.setElementTouchActionNone( this.element );
12960
+
12378
12961
  /**
12379
12962
  * Tracks mouse/touch/key events accross the group of buttons.
12380
12963
  * @member {OpenSeadragon.MouseTracker} tracker
@@ -12398,23 +12981,7 @@ $.ButtonGroup = function( options ) {
12398
12981
  }
12399
12982
  }
12400
12983
  },
12401
- pressHandler: function ( event ) {
12402
- if ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) {
12403
- var i;
12404
- for ( i = 0; i < _this.buttons.length; i++ ) {
12405
- _this.buttons[ i ].notifyGroupEnter();
12406
- }
12407
- }
12408
- },
12409
- releaseHandler: function ( event ) {
12410
- var i;
12411
- if ( !event.insideElementReleased || ( event.pointerType === 'touch' && !$.MouseTracker.haveTouchEnter ) ) {
12412
- for ( i = 0; i < _this.buttons.length; i++ ) {
12413
- _this.buttons[ i ].notifyGroupExit();
12414
- }
12415
- }
12416
- }
12417
- }).setTracking( true );
12984
+ });
12418
12985
  };
12419
12986
 
12420
12987
  $.ButtonGroup.prototype = /** @lends OpenSeadragon.ButtonGroup.prototype */{
@@ -12623,6 +13190,22 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
12623
13190
  ( this.height === other.height );
12624
13191
  },
12625
13192
 
13193
+ /**
13194
+ * Multiply all dimensions in this Rect by a factor and return a new Rect.
13195
+ * @function
13196
+ * @param {Number} factor The factor to multiply vector components.
13197
+ * @returns {OpenSeadragon.Rect} A new rect representing the multiplication
13198
+ * of the vector components by the factor
13199
+ */
13200
+ times: function( factor ) {
13201
+ return new OpenSeadragon.Rect(
13202
+ this.x * factor,
13203
+ this.y * factor,
13204
+ this.width * factor,
13205
+ this.height * factor
13206
+ );
13207
+ },
13208
+
12626
13209
  /**
12627
13210
  * Rotates a rectangle around a point. Currently only 90, 180, and 270
12628
13211
  * degrees are supported.
@@ -12639,7 +13222,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
12639
13222
  newTopLeft;
12640
13223
 
12641
13224
  degrees = ( degrees + 360 ) % 360;
12642
- if( degrees % 90 !== 0 ) {
13225
+ if (degrees % 90 !== 0) {
12643
13226
  throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
12644
13227
  }
12645
13228
 
@@ -12813,6 +13396,8 @@ $.ReferenceStrip = function ( options ) {
12813
13396
  style.background = '#000';
12814
13397
  style.position = 'relative';
12815
13398
 
13399
+ $.setElementTouchActionNone( this.element );
13400
+
12816
13401
  $.setElementOpacity( this.element, 0.8 );
12817
13402
 
12818
13403
  this.viewer = viewer;
@@ -12822,8 +13407,9 @@ $.ReferenceStrip = function ( options ) {
12822
13407
  scrollHandler: $.delegate( this, onStripScroll ),
12823
13408
  enterHandler: $.delegate( this, onStripEnter ),
12824
13409
  exitHandler: $.delegate( this, onStripExit ),
13410
+ keyDownHandler: $.delegate( this, onKeyDown ),
12825
13411
  keyHandler: $.delegate( this, onKeyPress )
12826
- } ).setTracking( true );
13412
+ } );
12827
13413
 
12828
13414
  //Controls the position and orientation of the reference strip and sets the
12829
13415
  //appropriate width and height
@@ -12888,6 +13474,7 @@ $.ReferenceStrip = function ( options ) {
12888
13474
  element.style.cssFloat = 'left'; //Firefox
12889
13475
  element.style.styleFloat = 'left'; //IE
12890
13476
  element.style.padding = '2px';
13477
+ $.setElementTouchActionNone( element );
12891
13478
 
12892
13479
  element.innerTracker = new $.MouseTracker( {
12893
13480
  element: element,
@@ -12910,7 +13497,7 @@ $.ReferenceStrip = function ( options ) {
12910
13497
  viewer.goToPage( page );
12911
13498
  }
12912
13499
  }
12913
- } ).setTracking( true );
13500
+ } );
12914
13501
 
12915
13502
  this.element.appendChild( element );
12916
13503
 
@@ -13142,8 +13729,10 @@ function loadPanels( strip, viewerSize, scroll ) {
13142
13729
  style.width = ( strip.panelWidth - 4 ) + 'px';
13143
13730
  style.height = ( strip.panelHeight - 4 ) + 'px';
13144
13731
 
13732
+ // TODO: What is this for? Future keyboard navigation support?
13145
13733
  miniViewer.displayRegion.innerTracker = new $.MouseTracker( {
13146
- element: miniViewer.displayRegion
13734
+ element: miniViewer.displayRegion,
13735
+ startDisabled: true
13147
13736
  } );
13148
13737
 
13149
13738
  element.getElementsByTagName( 'div' )[0].appendChild(
@@ -13207,6 +13796,37 @@ function onStripExit( event ) {
13207
13796
  }
13208
13797
 
13209
13798
 
13799
+ /**
13800
+ * @private
13801
+ * @inner
13802
+ * @function
13803
+ */
13804
+ function onKeyDown( event ) {
13805
+ //console.log( event.keyCode );
13806
+
13807
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
13808
+ switch ( event.keyCode ) {
13809
+ case 38: //up arrow
13810
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13811
+ return false;
13812
+ case 40: //down arrow
13813
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13814
+ return false;
13815
+ case 37: //left arrow
13816
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13817
+ return false;
13818
+ case 39: //right arrow
13819
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13820
+ return false;
13821
+ default:
13822
+ //console.log( 'navigator keycode %s', event.keyCode );
13823
+ return true;
13824
+ }
13825
+ } else {
13826
+ return true;
13827
+ }
13828
+ }
13829
+
13210
13830
 
13211
13831
  /**
13212
13832
  * @private
@@ -13216,35 +13836,35 @@ function onStripExit( event ) {
13216
13836
  function onKeyPress( event ) {
13217
13837
  //console.log( event.keyCode );
13218
13838
 
13219
- switch ( event.keyCode ) {
13220
- case 61: //=|+
13221
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13222
- return false;
13223
- case 45: //-|_
13224
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13225
- return false;
13226
- case 48: //0|)
13227
- case 119: //w
13228
- case 87: //W
13229
- case 38: //up arrow
13230
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13231
- return false;
13232
- case 115: //s
13233
- case 83: //S
13234
- case 40: //down arrow
13235
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13236
- return false;
13237
- case 97: //a
13238
- case 37: //left arrow
13239
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13240
- return false;
13241
- case 100: //d
13242
- case 39: //right arrow
13243
- onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13244
- return false;
13245
- default:
13246
- //console.log( 'navigator keycode %s', event.keyCode );
13247
- return true;
13839
+ if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
13840
+ switch ( event.keyCode ) {
13841
+ case 61: //=|+
13842
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13843
+ return false;
13844
+ case 45: //-|_
13845
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13846
+ return false;
13847
+ case 48: //0|)
13848
+ case 119: //w
13849
+ case 87: //W
13850
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13851
+ return false;
13852
+ case 115: //s
13853
+ case 83: //S
13854
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13855
+ return false;
13856
+ case 97: //a
13857
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: -1, shift: null } );
13858
+ return false;
13859
+ case 100: //d
13860
+ onStripScroll.call( this, { eventSource: this.tracker, position: null, scroll: 1, shift: null } );
13861
+ return false;
13862
+ default:
13863
+ //console.log( 'navigator keycode %s', event.keyCode );
13864
+ return true;
13865
+ }
13866
+ } else {
13867
+ return true;
13248
13868
  }
13249
13869
  }
13250
13870
 
@@ -13430,72 +14050,254 @@ $.Spring = function( options ) {
13430
14050
  * @property {Number} value
13431
14051
  * @property {Number} time
13432
14052
  */
13433
- this.target = {
13434
- value: this.current.value,
13435
- time: this.current.time
13436
- };
13437
- };
14053
+ this.target = {
14054
+ value: this.current.value,
14055
+ time: this.current.time
14056
+ };
14057
+ };
14058
+
14059
+ $.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
14060
+
14061
+ /**
14062
+ * @function
14063
+ * @param {Number} target
14064
+ */
14065
+ resetTo: function( target ) {
14066
+ this.target.value = target;
14067
+ this.target.time = this.current.time;
14068
+ this.start.value = this.target.value;
14069
+ this.start.time = this.target.time;
14070
+ },
14071
+
14072
+ /**
14073
+ * @function
14074
+ * @param {Number} target
14075
+ */
14076
+ springTo: function( target ) {
14077
+ this.start.value = this.current.value;
14078
+ this.start.time = this.current.time;
14079
+ this.target.value = target;
14080
+ this.target.time = this.start.time + 1000 * this.animationTime;
14081
+ },
14082
+
14083
+ /**
14084
+ * @function
14085
+ * @param {Number} delta
14086
+ */
14087
+ shiftBy: function( delta ) {
14088
+ this.start.value += delta;
14089
+ this.target.value += delta;
14090
+ },
14091
+
14092
+ /**
14093
+ * @function
14094
+ */
14095
+ update: function() {
14096
+ this.current.time = $.now();
14097
+ this.current.value = (this.current.time >= this.target.time) ?
14098
+ this.target.value :
14099
+ this.start.value +
14100
+ ( this.target.value - this.start.value ) *
14101
+ transform(
14102
+ this.springStiffness,
14103
+ ( this.current.time - this.start.time ) /
14104
+ ( this.target.time - this.start.time )
14105
+ );
14106
+ }
14107
+ };
14108
+
14109
+ /**
14110
+ * @private
14111
+ */
14112
+ function transform( stiffness, x ) {
14113
+ return ( 1.0 - Math.exp( stiffness * -x ) ) /
14114
+ ( 1.0 - Math.exp( -stiffness ) );
14115
+ }
14116
+
14117
+ }( OpenSeadragon ));
14118
+
14119
+ /*
14120
+ * OpenSeadragon - ImageLoader
14121
+ *
14122
+ * Copyright (C) 2009 CodePlex Foundation
14123
+ * Copyright (C) 2010-2013 OpenSeadragon contributors
14124
+
14125
+ * Redistribution and use in source and binary forms, with or without
14126
+ * modification, are permitted provided that the following conditions are
14127
+ * met:
14128
+ *
14129
+ * - Redistributions of source code must retain the above copyright notice,
14130
+ * this list of conditions and the following disclaimer.
14131
+ *
14132
+ * - Redistributions in binary form must reproduce the above copyright
14133
+ * notice, this list of conditions and the following disclaimer in the
14134
+ * documentation and/or other materials provided with the distribution.
14135
+ *
14136
+ * - Neither the name of CodePlex Foundation nor the names of its
14137
+ * contributors may be used to endorse or promote products derived from
14138
+ * this software without specific prior written permission.
14139
+ *
14140
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14141
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
14142
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
14143
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
14144
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
14145
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
14146
+ * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
14147
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
14148
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
14149
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
14150
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14151
+ */
14152
+
14153
+ (function( $ ){
14154
+
14155
+ /**
14156
+ * @private
14157
+ * @class ImageJob
14158
+ * @classdesc Handles loading a single image for use in a single {@link OpenSeadragon.Tile}.
14159
+ *
14160
+ * @memberof OpenSeadragon
14161
+ * @param {String} source - URL of image to download.
14162
+ * @param {String} crossOriginPolicy - CORS policy to use for downloads
14163
+ * @param {Function} callback - Called once image has finished downloading.
14164
+ */
14165
+ function ImageJob ( options ) {
14166
+
14167
+ $.extend( true, this, {
14168
+ timeout: $.DEFAULT_SETTINGS.timeout,
14169
+ jobId: null
14170
+ }, options );
14171
+
14172
+ /**
14173
+ * Image object which will contain downloaded image.
14174
+ * @member {Image} image
14175
+ * @memberof OpenSeadragon.ImageJob#
14176
+ */
14177
+ this.image = null;
14178
+ }
13438
14179
 
13439
- $.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
14180
+ ImageJob.prototype = {
13440
14181
 
13441
14182
  /**
13442
- * @function
13443
- * @param {Number} target
14183
+ * Initiates downloading of associated image.
14184
+ * @method
13444
14185
  */
13445
- resetTo: function( target ) {
13446
- this.target.value = target;
13447
- this.target.time = this.current.time;
13448
- this.start.value = this.target.value;
13449
- this.start.time = this.target.time;
13450
- },
14186
+ start: function(){
14187
+ var _this = this;
13451
14188
 
13452
- /**
13453
- * @function
13454
- * @param {Number} target
13455
- */
13456
- springTo: function( target ) {
13457
- this.start.value = this.current.value;
13458
- this.start.time = this.current.time;
13459
- this.target.value = target;
13460
- this.target.time = this.start.time + 1000 * this.animationTime;
14189
+ this.image = new Image();
14190
+
14191
+ if ( this.crossOriginPolicy !== false ) {
14192
+ this.image.crossOrigin = this.crossOriginPolicy;
14193
+ }
14194
+
14195
+ this.image.onload = function(){
14196
+ _this.finish( true );
14197
+ };
14198
+ this.image.onabort = this.image.onerror = function(){
14199
+ _this.finish( false );
14200
+ };
14201
+
14202
+ this.jobId = window.setTimeout( function(){
14203
+ _this.finish( false );
14204
+ }, this.timeout);
14205
+
14206
+ this.image.src = this.src;
13461
14207
  },
13462
14208
 
14209
+ finish: function( successful ) {
14210
+ this.image.onload = this.image.onerror = this.image.onabort = null;
14211
+ if (!successful) {
14212
+ this.image = null;
14213
+ }
14214
+
14215
+ if ( this.jobId ) {
14216
+ window.clearTimeout( this.jobId );
14217
+ }
14218
+
14219
+ this.callback( this );
14220
+ }
14221
+
14222
+ };
14223
+
14224
+ /**
14225
+ * @class
14226
+ * @classdesc Handles downloading of a set of images using asynchronous queue pattern.
14227
+ */
14228
+ $.ImageLoader = function() {
14229
+
14230
+ $.extend( true, this, {
14231
+ jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
14232
+ jobQueue: [],
14233
+ jobsInProgress: 0
14234
+ });
14235
+
14236
+ };
14237
+
14238
+ $.ImageLoader.prototype = {
14239
+
13463
14240
  /**
13464
- * @function
13465
- * @param {Number} delta
14241
+ * Add an unloaded image to the loader queue.
14242
+ * @method
14243
+ * @param {String} src - URL of image to download.
14244
+ * @param {String} crossOriginPolicy - CORS policy to use for downloads
14245
+ * @param {Function} callback - Called once image has been downloaded.
13466
14246
  */
13467
- shiftBy: function( delta ) {
13468
- this.start.value += delta;
13469
- this.target.value += delta;
14247
+ addJob: function( options ) {
14248
+ var _this = this,
14249
+ complete = function( job ) {
14250
+ completeJob( _this, job, options.callback );
14251
+ },
14252
+ jobOptions = {
14253
+ src: options.src,
14254
+ crossOriginPolicy: options.crossOriginPolicy,
14255
+ callback: complete
14256
+ },
14257
+ newJob = new ImageJob( jobOptions );
14258
+
14259
+ if ( !this.jobLimit || this.jobsInProgress < this.jobLimit ) {
14260
+ newJob.start();
14261
+ this.jobsInProgress++;
14262
+ }
14263
+ else {
14264
+ this.jobQueue.push( newJob );
14265
+ }
13470
14266
  },
13471
14267
 
13472
14268
  /**
13473
- * @function
14269
+ * Clear any unstarted image loading jobs from the queue.
14270
+ * @method
13474
14271
  */
13475
- update: function() {
13476
- this.current.time = $.now();
13477
- this.current.value = (this.current.time >= this.target.time) ?
13478
- this.target.value :
13479
- this.start.value +
13480
- ( this.target.value - this.start.value ) *
13481
- transform(
13482
- this.springStiffness,
13483
- ( this.current.time - this.start.time ) /
13484
- ( this.target.time - this.start.time )
13485
- );
14272
+ clear: function() {
14273
+ this.jobQueue = [];
13486
14274
  }
13487
14275
  };
13488
14276
 
13489
14277
  /**
14278
+ * Cleans up ImageJob once completed.
14279
+ * @method
13490
14280
  * @private
14281
+ * @param loader - ImageLoader used to start job.
14282
+ * @param job - The ImageJob that has completed.
14283
+ * @param callback - Called once cleanup is finished.
13491
14284
  */
13492
- function transform( stiffness, x ) {
13493
- return ( 1.0 - Math.exp( stiffness * -x ) ) /
13494
- ( 1.0 - Math.exp( -stiffness ) );
14285
+ function completeJob( loader, job, callback ) {
14286
+ var nextJob;
14287
+
14288
+ loader.jobsInProgress--;
14289
+
14290
+ if ( (!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.jobQueue.length > 0) {
14291
+ nextJob = loader.jobQueue.shift();
14292
+ nextJob.start();
14293
+ }
14294
+
14295
+ callback( job.image );
13495
14296
  }
13496
14297
 
13497
14298
  }( OpenSeadragon ));
13498
14299
 
14300
+
13499
14301
  /*
13500
14302
  * OpenSeadragon - Tile
13501
14303
  *
@@ -14192,7 +14994,7 @@ var DEVICE_SCREEN = $.getWindowSize(),
14192
14994
 
14193
14995
  /**
14194
14996
  * @class Drawer
14195
- * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
14997
+ * @classdesc Handles rendering of tiles for an {@link OpenSeadragon.Viewer}.
14196
14998
  * A new instance is created for each TileSource opened (see {@link OpenSeadragon.Viewer#drawer}).
14197
14999
  *
14198
15000
  * @memberof OpenSeadragon
@@ -14219,7 +15021,7 @@ $.Drawer = function( options ) {
14219
15021
 
14220
15022
  //internal state properties
14221
15023
  viewer: null,
14222
- downloading: 0, // How many images are currently being loaded in parallel.
15024
+ imageLoader: new $.ImageLoader(),
14223
15025
  tilesMatrix: {}, // A '3d' dictionary [level][x][y] --> Tile.
14224
15026
  tilesLoaded: [], // An unordered list of Tiles with loaded images.
14225
15027
  coverage: {}, // A '3d' dictionary [level][x][y] --> Boolean.
@@ -14235,7 +15037,6 @@ $.Drawer = function( options ) {
14235
15037
  //configurable settings
14236
15038
  opacity: $.DEFAULT_SETTINGS.opacity,
14237
15039
  maxImageCacheCount: $.DEFAULT_SETTINGS.maxImageCacheCount,
14238
- imageLoaderLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
14239
15040
  minZoomImageRatio: $.DEFAULT_SETTINGS.minZoomImageRatio,
14240
15041
  wrapHorizontal: $.DEFAULT_SETTINGS.wrapHorizontal,
14241
15042
  wrapVertical: $.DEFAULT_SETTINGS.wrapVertical,
@@ -14439,77 +15240,6 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
14439
15240
  return this;
14440
15241
  },
14441
15242
 
14442
- /**
14443
- * Used internally to load images when required. May also be used to
14444
- * preload a set of images so the browser will have them available in
14445
- * the local cache to optimize user experience in certain cases. Because
14446
- * the number of parallel image loads is configurable, if too many images
14447
- * are currently being loaded, the request will be ignored. Since by
14448
- * default drawer.imageLoaderLimit is 0, the native browser parallel
14449
- * image loading policy will be used.
14450
- * @method
14451
- * @param {String} src - The url of the image to load.
14452
- * @param {Function} callback - The function that will be called with the
14453
- * Image object as the only parameter if it was loaded successfully.
14454
- * If an error occured, or the request timed out or was aborted,
14455
- * the parameter is null instead.
14456
- * @return {Boolean} loading - Whether the request was submitted or ignored
14457
- * based on OpenSeadragon.DEFAULT_SETTINGS.imageLoaderLimit.
14458
- */
14459
- loadImage: function( src, callback ) {
14460
- var _this = this,
14461
- loading = false,
14462
- image,
14463
- jobid,
14464
- complete;
14465
-
14466
- if ( !this.imageLoaderLimit ||
14467
- this.downloading < this.imageLoaderLimit ) {
14468
-
14469
- this.downloading++;
14470
-
14471
- image = new Image();
14472
-
14473
- if ( _this.crossOriginPolicy !== false ) {
14474
- image.crossOrigin = _this.crossOriginPolicy;
14475
- }
14476
-
14477
- complete = function( imagesrc, resultingImage ){
14478
- _this.downloading--;
14479
- if (typeof ( callback ) == "function") {
14480
- try {
14481
- callback( resultingImage );
14482
- } catch ( e ) {
14483
- $.console.error(
14484
- "%s while executing %s callback: %s",
14485
- e.name,
14486
- src,
14487
- e.message,
14488
- e
14489
- );
14490
- }
14491
- }
14492
- };
14493
-
14494
- image.onload = function(){
14495
- finishLoadingImage( image, complete, true, jobid );
14496
- };
14497
-
14498
- image.onabort = image.onerror = function(){
14499
- finishLoadingImage( image, complete, false, jobid );
14500
- };
14501
-
14502
- jobid = window.setTimeout( function(){
14503
- finishLoadingImage( image, complete, false, jobid );
14504
- }, this.timeout );
14505
-
14506
- loading = true;
14507
- image.src = src;
14508
- }
14509
-
14510
- return loading;
14511
- },
14512
-
14513
15243
  /**
14514
15244
  * Returns whether rotation is supported or not.
14515
15245
  * @method
@@ -14517,6 +15247,22 @@ $.Drawer.prototype = /** @lends OpenSeadragon.Drawer.prototype */{
14517
15247
  */
14518
15248
  canRotate: function() {
14519
15249
  return this.useCanvas;
15250
+ },
15251
+
15252
+ /**
15253
+ * Destroy the drawer (unload current loaded tiles)
15254
+ * @method
15255
+ * @return null
15256
+ */
15257
+ destroy: function() {
15258
+ //unload current loaded tiles (=empty TILE_CACHE)
15259
+ for ( var i = 0; i < this.tilesLoaded.length; ++i ) {
15260
+ this.tilesLoaded[i].unload();
15261
+ }
15262
+
15263
+ //force unloading of current canvas (1x1 will be gc later, trick not necessarily needed)
15264
+ this.canvas.width = 1;
15265
+ this.canvas.height = 1;
14520
15266
  }
14521
15267
  };
14522
15268
 
@@ -14579,13 +15325,13 @@ function updateViewport( drawer ) {
14579
15325
  levelOpacity,
14580
15326
  levelVisibility;
14581
15327
 
14582
- //TODO
15328
+ // Reset tile's internal drawn state
14583
15329
  while ( drawer.lastDrawn.length > 0 ) {
14584
15330
  tile = drawer.lastDrawn.pop();
14585
15331
  tile.beingDrawn = false;
14586
15332
  }
14587
15333
 
14588
- //TODO
15334
+ // Clear canvas
14589
15335
  drawer.canvas.innerHTML = "";
14590
15336
  if ( drawer.useCanvas ) {
14591
15337
  if( drawer.canvas.width != viewportSize.x ||
@@ -14602,6 +15348,16 @@ function updateViewport( drawer ) {
14602
15348
  viewportTL = rotatedBounds.getTopLeft();
14603
15349
  viewportBR = rotatedBounds.getBottomRight();
14604
15350
  }
15351
+ else if (degrees !== 0) {
15352
+ // This is just an approximation.
15353
+ var orthBounds = viewportBounds.rotate(90);
15354
+ viewportBounds.x -= orthBounds.width / 2;
15355
+ viewportBounds.y -= orthBounds.height / 2;
15356
+ viewportBounds.width += orthBounds.width;
15357
+ viewportBounds.height += orthBounds.height;
15358
+ viewportTL = viewportBounds.getTopLeft();
15359
+ viewportBR = viewportBounds.getBottomRight();
15360
+ }
14605
15361
 
14606
15362
  //Don't draw if completely outside of the viewport
14607
15363
  if ( !drawer.wrapHorizontal &&
@@ -14613,7 +15369,7 @@ function updateViewport( drawer ) {
14613
15369
  return;
14614
15370
  }
14615
15371
 
14616
- //TODO
15372
+ // Calculate viewport rect / bounds
14617
15373
  if ( !drawer.wrapHorizontal ) {
14618
15374
  viewportTL.x = Math.max( viewportTL.x, 0 );
14619
15375
  viewportBR.x = Math.min( viewportBR.x, 1 );
@@ -14623,10 +15379,12 @@ function updateViewport( drawer ) {
14623
15379
  viewportBR.y = Math.min( viewportBR.y, drawer.normHeight );
14624
15380
  }
14625
15381
 
14626
- //TODO
15382
+ // Calculations for the interval of levels to draw
15383
+ // (above in initial var statement)
15384
+ // can return invalid intervals; fix that here if necessary
14627
15385
  lowestLevel = Math.min( lowestLevel, highestLevel );
14628
15386
 
14629
- //TODO
15387
+ // Update any level that will be drawn
14630
15388
  var drawLevel; // FIXME: drawLevel should have a more explanatory name
14631
15389
  for ( level = highestLevel; level >= lowestLevel; level-- ) {
14632
15390
  drawLevel = false;
@@ -14671,7 +15429,7 @@ function updateViewport( drawer ) {
14671
15429
  optimalRatio - renderPixelRatioT
14672
15430
  );
14673
15431
 
14674
- //TODO
15432
+ // Update the level and keep track of 'best' tile to load
14675
15433
  best = updateLevel(
14676
15434
  drawer,
14677
15435
  haveDrawn,
@@ -14685,16 +15443,17 @@ function updateViewport( drawer ) {
14685
15443
  best
14686
15444
  );
14687
15445
 
14688
- //TODO
15446
+ // Stop the loop if lower-res tiles would all be covered by
15447
+ // already drawn tiles
14689
15448
  if ( providesCoverage( drawer.coverage, level ) ) {
14690
15449
  break;
14691
15450
  }
14692
15451
  }
14693
15452
 
14694
- //TODO
15453
+ // Perform the actual drawing
14695
15454
  drawTiles( drawer, drawer.lastDrawn );
14696
15455
 
14697
- //TODO
15456
+ // Load the new 'best' tile
14698
15457
  if ( best ) {
14699
15458
  loadTile( drawer, best, currentTime );
14700
15459
  // because we haven't finished drawing, so
@@ -14899,39 +15658,27 @@ function getTile( x, y, level, tileSource, tilesMatrix, time, numTiles, normHeig
14899
15658
  return tile;
14900
15659
  }
14901
15660
 
14902
-
14903
15661
  function loadTile( drawer, tile, time ) {
14904
15662
  if( drawer.viewport.collectionMode ){
14905
15663
  drawer.midUpdate = false;
14906
15664
  onTileLoad( drawer, tile, time );
14907
15665
  } else {
14908
- tile.loading = drawer.loadImage(
14909
- tile.url,
14910
- function( image ){
15666
+ tile.loading = true;
15667
+ drawer.imageLoader.addJob({
15668
+ src: tile.url,
15669
+ crossOriginPolicy: drawer.crossOriginPolicy,
15670
+ callback: function( image ){
14911
15671
  onTileLoad( drawer, tile, time, image );
14912
15672
  }
14913
- );
15673
+ });
14914
15674
  }
14915
15675
  }
14916
15676
 
14917
15677
  function onTileLoad( drawer, tile, time, image ) {
14918
- var insertionIndex,
14919
- cutoff,
14920
- worstTile,
14921
- worstTime,
14922
- worstLevel,
14923
- worstTileIndex,
14924
- prevTile,
14925
- prevTime,
14926
- prevLevel,
14927
- i;
14928
15678
 
14929
15679
  tile.loading = false;
14930
15680
 
14931
- if ( drawer.midUpdate ) {
14932
- $.console.warn( "Tile load callback in middle of drawing routine." );
14933
- return;
14934
- } else if ( !image && !drawer.viewport.collectionMode ) {
15681
+ if ( !image && !drawer.viewport.collectionMode ) {
14935
15682
  $.console.log( "Tile %s failed to load: %s", tile, tile.url );
14936
15683
  if( !drawer.debugMode ){
14937
15684
  tile.exists = false;
@@ -14945,46 +15692,62 @@ function onTileLoad( drawer, tile, time, image ) {
14945
15692
  tile.loaded = true;
14946
15693
  tile.image = image;
14947
15694
 
15695
+ if ( drawer.tilesLoaded.length < drawer.maxImageCacheCount ) {
15696
+ // always safe to append things to cache
15697
+ drawer.tilesLoaded[ drawer.tilesLoaded.length ] = tile;
15698
+ }
15699
+ else {
15700
+ // need to remove something from cache,
15701
+ // make sure this doesn't happen mid update
15702
+ if ( !drawer.midUpdate ) {
15703
+ updateTileCache( tile, drawer );
15704
+ }
15705
+ else {
15706
+ window.setTimeout( function() {
15707
+ updateTileCache( tile, drawer );
15708
+ }, 1);
15709
+ }
15710
+ }
14948
15711
 
14949
- insertionIndex = drawer.tilesLoaded.length;
14950
-
14951
- if ( drawer.tilesLoaded.length >= drawer.maxImageCacheCount ) {
14952
- cutoff = Math.ceil( Math.log( drawer.source.tileSize ) / Math.log( 2 ) );
15712
+ drawer.updateAgain = true;
15713
+ }
14953
15714
 
14954
- worstTile = null;
15715
+ function updateTileCache( newTile, drawer ) {
15716
+ var i, prevTile, prevTime, worstTime, prevLevel, worstLevel,
15717
+ insertionIndex = drawer.tilesLoaded.length,
15718
+ cutoff = Math.ceil( Math.log( drawer.source.getTileSize(newTile.level) ) / Math.log( 2 ) ),
15719
+ worstTile = null,
14955
15720
  worstTileIndex = -1;
14956
15721
 
14957
- for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) {
14958
- prevTile = drawer.tilesLoaded[ i ];
15722
+ for ( i = drawer.tilesLoaded.length - 1; i >= 0; i-- ) {
15723
+ prevTile = drawer.tilesLoaded[ i ];
14959
15724
 
14960
- if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) {
14961
- continue;
14962
- } else if ( !worstTile ) {
14963
- worstTile = prevTile;
14964
- worstTileIndex = i;
14965
- continue;
14966
- }
15725
+ if ( prevTile.level <= drawer.cutoff || prevTile.beingDrawn ) {
15726
+ continue;
15727
+ } else if ( !worstTile ) {
15728
+ worstTile = prevTile;
15729
+ worstTileIndex = i;
15730
+ continue;
15731
+ }
14967
15732
 
14968
- prevTime = prevTile.lastTouchTime;
14969
- worstTime = worstTile.lastTouchTime;
14970
- prevLevel = prevTile.level;
14971
- worstLevel = worstTile.level;
15733
+ prevTime = prevTile.lastTouchTime;
15734
+ worstTime = worstTile.lastTouchTime;
15735
+ prevLevel = prevTile.level;
15736
+ worstLevel = worstTile.level;
14972
15737
 
14973
- if ( prevTime < worstTime ||
14974
- ( prevTime == worstTime && prevLevel > worstLevel ) ) {
14975
- worstTile = prevTile;
14976
- worstTileIndex = i;
14977
- }
14978
- }
15738
+ if ( prevTime < worstTime ||
15739
+ ( prevTime == worstTime && prevLevel > worstLevel ) ) {
15740
+ worstTile = prevTile;
15741
+ worstTileIndex = i;
15742
+ }
15743
+ }
14979
15744
 
14980
- if ( worstTile && worstTileIndex >= 0 ) {
14981
- worstTile.unload();
14982
- insertionIndex = worstTileIndex;
14983
- }
15745
+ if ( worstTile && worstTileIndex >= 0 ) {
15746
+ worstTile.unload();
15747
+ insertionIndex = worstTileIndex;
14984
15748
  }
14985
15749
 
14986
- drawer.tilesLoaded[ insertionIndex ] = tile;
14987
- drawer.updateAgain = true;
15750
+ drawer.tilesLoaded[ insertionIndex ] = newTile;
14988
15751
  }
14989
15752
 
14990
15753
 
@@ -15161,21 +15924,6 @@ function compareTiles( previousBest, tile ) {
15161
15924
  return previousBest;
15162
15925
  }
15163
15926
 
15164
- function finishLoadingImage( image, callback, successful, jobid ){
15165
-
15166
- image.onload = null;
15167
- image.onabort = null;
15168
- image.onerror = null;
15169
-
15170
- if ( jobid ) {
15171
- window.clearTimeout( jobid );
15172
- }
15173
- $.requestAnimationFrame( function() {
15174
- callback( image.src, successful ? image : null);
15175
- });
15176
-
15177
- }
15178
-
15179
15927
  function drawTiles( drawer, lastDrawn ){
15180
15928
  var i,
15181
15929
  tile,
@@ -15350,12 +16098,24 @@ function drawDebugInfo( drawer, tile, count, i ){
15350
16098
  drawer.context.font = 'small-caps bold 13px ariel';
15351
16099
  drawer.context.strokeStyle = drawer.debugGridColor;
15352
16100
  drawer.context.fillStyle = drawer.debugGridColor;
16101
+
16102
+ offsetForRotation( tile, drawer.canvas, drawer.context, drawer.viewport.degrees );
16103
+
15353
16104
  drawer.context.strokeRect(
15354
16105
  tile.position.x,
15355
16106
  tile.position.y,
15356
16107
  tile.size.x,
15357
16108
  tile.size.y
15358
16109
  );
16110
+
16111
+ var tileCenterX = tile.position.x + (tile.size.x / 2);
16112
+ var tileCenterY = tile.position.y + (tile.size.y / 2);
16113
+
16114
+ // Rotate the text the right way around.
16115
+ drawer.context.translate( tileCenterX, tileCenterY );
16116
+ drawer.context.rotate( Math.PI / 180 * -drawer.viewport.degrees );
16117
+ drawer.context.translate( -tileCenterX, -tileCenterY );
16118
+
15359
16119
  if( tile.x === 0 && tile.y === 0 ){
15360
16120
  drawer.context.fillText(
15361
16121
  "Zoom: " + drawer.viewport.getZoom(),
@@ -15398,6 +16158,7 @@ function drawDebugInfo( drawer, tile, count, i ){
15398
16158
  tile.position.x + 10,
15399
16159
  tile.position.y + 70
15400
16160
  );
16161
+ restoreRotationChanges( tile, drawer.canvas, drawer.context );
15401
16162
  drawer.context.restore();
15402
16163
  }
15403
16164
  }
@@ -15491,7 +16252,8 @@ $.Viewport = function( options ) {
15491
16252
  defaultZoomLevel: $.DEFAULT_SETTINGS.defaultZoomLevel,
15492
16253
  minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
15493
16254
  maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
15494
- degrees: $.DEFAULT_SETTINGS.degrees
16255
+ degrees: $.DEFAULT_SETTINGS.degrees,
16256
+ homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer
15495
16257
 
15496
16258
  }, options );
15497
16259
 
@@ -15555,15 +16317,21 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15555
16317
  * @function
15556
16318
  */
15557
16319
  getHomeZoom: function() {
15558
- var aspectFactor =
15559
- this.contentAspectX / this.getAspectRatio();
15560
-
15561
16320
  if( this.defaultZoomLevel ){
15562
16321
  return this.defaultZoomLevel;
15563
16322
  } else {
15564
- return ( aspectFactor >= 1 ) ?
15565
- 1 :
15566
- aspectFactor;
16323
+ var aspectFactor =
16324
+ this.contentAspectX / this.getAspectRatio();
16325
+
16326
+ if( this.homeFillsViewer ){ // fill the viewer and clip the image
16327
+ return ( aspectFactor >= 1) ?
16328
+ aspectFactor :
16329
+ 1;
16330
+ } else {
16331
+ return ( aspectFactor >= 1 ) ?
16332
+ 1 :
16333
+ aspectFactor;
16334
+ }
15567
16335
  }
15568
16336
  },
15569
16337
 
@@ -15729,38 +16497,34 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15729
16497
 
15730
16498
  /**
15731
16499
  * @function
15732
- * @return {OpenSeadragon.Viewport} Chainable.
15733
- * @fires OpenSeadragon.Viewer.event:constrain
16500
+ * @private
16501
+ * @param {OpenSeadragon.Rect} bounds
16502
+ * @param {Boolean} immediately
16503
+ * @return {OpenSeadragon.Rect} constrained bounds.
15734
16504
  */
15735
- applyConstraints: function( immediately ) {
15736
- var actualZoom = this.getZoom(),
15737
- constrainedZoom = Math.max(
15738
- Math.min( actualZoom, this.getMaxZoom() ),
15739
- this.getMinZoom()
15740
- ),
15741
- bounds,
15742
- horizontalThreshold,
16505
+ _applyBoundaryConstraints: function( bounds, immediately ) {
16506
+ var horizontalThreshold,
15743
16507
  verticalThreshold,
15744
16508
  left,
15745
16509
  right,
15746
16510
  top,
15747
16511
  bottom,
15748
16512
  dx = 0,
15749
- dy = 0;
15750
-
15751
- if ( actualZoom != constrainedZoom ) {
15752
- this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
15753
- }
15754
-
15755
- bounds = this.getBounds();
16513
+ dy = 0,
16514
+ newBounds = new $.Rect(
16515
+ bounds.x,
16516
+ bounds.y,
16517
+ bounds.width,
16518
+ bounds.height
16519
+ );
15756
16520
 
15757
- horizontalThreshold = this.visibilityRatio * bounds.width;
15758
- verticalThreshold = this.visibilityRatio * bounds.height;
16521
+ horizontalThreshold = this.visibilityRatio * newBounds.width;
16522
+ verticalThreshold = this.visibilityRatio * newBounds.height;
15759
16523
 
15760
- left = bounds.x + bounds.width;
15761
- right = 1 - bounds.x;
15762
- top = bounds.y + bounds.height;
15763
- bottom = this.contentAspectY - bounds.y;
16524
+ left = newBounds.x + newBounds.width;
16525
+ right = 1 - newBounds.x;
16526
+ top = newBounds.y + newBounds.height;
16527
+ bottom = this.contentAspectY - newBounds.y;
15764
16528
 
15765
16529
  if ( this.wrapHorizontal ) {
15766
16530
  //do nothing
@@ -15789,15 +16553,14 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15789
16553
  }
15790
16554
 
15791
16555
  if ( dx || dy || immediately ) {
15792
- bounds.x += dx;
15793
- bounds.y += dy;
15794
- if( bounds.width > 1 ){
15795
- bounds.x = 0.5 - bounds.width/2;
16556
+ newBounds.x += dx;
16557
+ newBounds.y += dy;
16558
+ if( newBounds.width > 1 ){
16559
+ newBounds.x = 0.5 - newBounds.width/2;
15796
16560
  }
15797
- if( bounds.height > this.contentAspectY ){
15798
- bounds.y = this.contentAspectY/2 - bounds.height/2;
16561
+ if( newBounds.height > this.contentAspectY ){
16562
+ newBounds.y = this.contentAspectY/2 - newBounds.height/2;
15799
16563
  }
15800
- this.fitBounds( bounds, immediately );
15801
16564
  }
15802
16565
 
15803
16566
  if( this.viewer ){
@@ -15816,6 +16579,35 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15816
16579
  });
15817
16580
  }
15818
16581
 
16582
+ return newBounds;
16583
+ },
16584
+
16585
+ /**
16586
+ * @function
16587
+ * @return {OpenSeadragon.Viewport} Chainable.
16588
+ * @fires OpenSeadragon.Viewer.event:constrain
16589
+ */
16590
+ applyConstraints: function( immediately ) {
16591
+ var actualZoom = this.getZoom(),
16592
+ constrainedZoom = Math.max(
16593
+ Math.min( actualZoom, this.getMaxZoom() ),
16594
+ this.getMinZoom()
16595
+ ),
16596
+ bounds,
16597
+ constrainedBounds;
16598
+
16599
+ if ( actualZoom != constrainedZoom ) {
16600
+ this.zoomTo( constrainedZoom, this.zoomPoint, immediately );
16601
+ }
16602
+
16603
+ bounds = this.getBounds();
16604
+
16605
+ constrainedBounds = this._applyBoundaryConstraints( bounds, immediately );
16606
+
16607
+ if ( bounds.x !== constrainedBounds.x || bounds.y !== constrainedBounds.y || immediately ){
16608
+ this.fitBounds( constrainedBounds, immediately );
16609
+ }
16610
+
15819
16611
  return this;
15820
16612
  },
15821
16613
 
@@ -15829,11 +16621,16 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15829
16621
 
15830
16622
  /**
15831
16623
  * @function
16624
+ * @private
15832
16625
  * @param {OpenSeadragon.Rect} bounds
15833
- * @param {Boolean} immediately
16626
+ * @param {Object} options (immediately=false, constraints=false)
15834
16627
  * @return {OpenSeadragon.Viewport} Chainable.
15835
16628
  */
15836
- fitBounds: function( bounds, immediately ) {
16629
+ _fitBounds: function( bounds, options ) {
16630
+ options = options || {};
16631
+ var immediately = options.immediately || false;
16632
+ var constraints = options.constraints || false;
16633
+
15837
16634
  var aspect = this.getAspectRatio(),
15838
16635
  center = bounds.getCenter(),
15839
16636
  newBounds = new $.Rect(
@@ -15845,7 +16642,9 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15845
16642
  oldBounds,
15846
16643
  oldZoom,
15847
16644
  newZoom,
15848
- referencePoint;
16645
+ referencePoint,
16646
+ newBoundsAspectRatio,
16647
+ newConstrainedZoom;
15849
16648
 
15850
16649
  if ( newBounds.getAspectRatio() >= aspect ) {
15851
16650
  newBounds.height = bounds.width / aspect;
@@ -15855,14 +16654,36 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15855
16654
  newBounds.x = center.x - newBounds.width / 2;
15856
16655
  }
15857
16656
 
16657
+ if ( constraints ) {
16658
+ newBoundsAspectRatio = newBounds.getAspectRatio();
16659
+ }
16660
+
15858
16661
  this.panTo( this.getCenter( true ), true );
15859
16662
  this.zoomTo( this.getZoom( true ), null, true );
15860
16663
 
15861
16664
  oldBounds = this.getBounds();
15862
16665
  oldZoom = this.getZoom();
15863
16666
  newZoom = 1.0 / newBounds.width;
16667
+
16668
+ if ( constraints ) {
16669
+ newConstrainedZoom = Math.max(
16670
+ Math.min(newZoom, this.getMaxZoom() ),
16671
+ this.getMinZoom()
16672
+ );
16673
+
16674
+ if (newZoom !== newConstrainedZoom) {
16675
+ newZoom = newConstrainedZoom;
16676
+ newBounds.width = 1.0 / newZoom;
16677
+ newBounds.x = center.x - newBounds.width / 2;
16678
+ newBounds.height = newBounds.width / newBoundsAspectRatio;
16679
+ newBounds.y = center.y - newBounds.height / 2;
16680
+ }
16681
+
16682
+ newBounds = this._applyBoundaryConstraints( newBounds, immediately );
16683
+ }
16684
+
15864
16685
  if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) {
15865
- return this.panTo( center, immediately );
16686
+ return this.panTo( constraints ? newBounds.getCenter() : center, immediately );
15866
16687
  }
15867
16688
 
15868
16689
  referencePoint = oldBounds.getTopLeft().times(
@@ -15879,6 +16700,31 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
15879
16700
  return this.zoomTo( newZoom, referencePoint, immediately );
15880
16701
  },
15881
16702
 
16703
+ /**
16704
+ * @function
16705
+ * @param {OpenSeadragon.Rect} bounds
16706
+ * @param {Boolean} immediately
16707
+ * @return {OpenSeadragon.Viewport} Chainable.
16708
+ */
16709
+ fitBounds: function( bounds, immediately ) {
16710
+ return this._fitBounds( bounds, {
16711
+ immediately: immediately,
16712
+ constraints: false
16713
+ } );
16714
+ },
16715
+
16716
+ /**
16717
+ * @function
16718
+ * @param {OpenSeadragon.Rect} bounds
16719
+ * @param {Boolean} immediately
16720
+ * @return {OpenSeadragon.Viewport} Chainable.
16721
+ */
16722
+ fitBoundsWithConstraints: function( bounds, immediately ) {
16723
+ return this._fitBounds( bounds, {
16724
+ immediately: immediately,
16725
+ constraints: true
16726
+ } );
16727
+ },
15882
16728
 
15883
16729
  /**
15884
16730
  * @function
@@ -16042,10 +16888,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16042
16888
  },
16043
16889
 
16044
16890
  /**
16045
- * Currently only 90 degree rotation is supported and it only works
16046
- * with the canvas. Additionally, the navigator does not rotate yet,
16047
- * debug mode doesn't rotate yet, and overlay rotation is only
16048
- * partially supported.
16891
+ * Rotates this viewport to the angle specified.
16049
16892
  * @function
16050
16893
  * @return {OpenSeadragon.Viewport} Chainable.
16051
16894
  */
@@ -16055,12 +16898,23 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16055
16898
  }
16056
16899
 
16057
16900
  degrees = ( degrees + 360 ) % 360;
16058
- if( degrees % 90 !== 0 ) {
16059
- throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.');
16060
- }
16061
16901
  this.degrees = degrees;
16062
16902
  this.viewer.forceRedraw();
16063
-
16903
+
16904
+ /**
16905
+ * Raised when rotation has been changed.
16906
+ *
16907
+ * @event update-viewport
16908
+ * @memberof OpenSeadragon.Viewer
16909
+ * @type {object}
16910
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
16911
+ * @property {Number} degrees - The number of degrees the rotation was set to.
16912
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
16913
+ */
16914
+ if (this.viewer !== null)
16915
+ {
16916
+ this.viewer.raiseEvent('rotate', {"degrees": degrees});
16917
+ }
16064
16918
  return this;
16065
16919
  },
16066
16920
 
@@ -16398,7 +17252,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16398
17252
  return viewerCoordinates.plus(
16399
17253
  OpenSeadragon.getElementPosition( this.viewer.element ));
16400
17254
  },
16401
-
17255
+
16402
17256
  /**
16403
17257
  * Convert a viewport zoom to an image zoom.
16404
17258
  * Image zoom: ratio of the original image size to displayed image size.
@@ -16416,7 +17270,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
16416
17270
  var viewportToImageZoomRatio = containerWidth / imageWidth;
16417
17271
  return viewportZoom * viewportToImageZoomRatio;
16418
17272
  },
16419
-
17273
+
16420
17274
  /**
16421
17275
  * Convert an image zoom to a viewport zoom.
16422
17276
  * Image zoom: ratio of the original image size to displayed image size.