openseadragon 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
- //! openseadragon 4.0.0
2
- //! Built on 2022-12-16
3
- //! Git commit: v4.0.0-0-8e6196a
1
+ //! openseadragon 4.1.0
2
+ //! Built on 2023-05-25
3
+ //! Git commit: v4.1.0-0-8849681
4
4
  //! http://openseadragon.github.io
5
5
  //! License: http://openseadragon.github.io/license/
6
6
 
@@ -90,7 +90,7 @@
90
90
 
91
91
  /**
92
92
  * @namespace OpenSeadragon
93
- * @version openseadragon 4.0.0
93
+ * @version openseadragon 4.1.0
94
94
  * @classdesc The root namespace for OpenSeadragon. All utility methods
95
95
  * and classes are defined on or below this namespace.
96
96
  *
@@ -501,6 +501,12 @@
501
501
  * @property {Number} [timeout=30000]
502
502
  * The max number of milliseconds that an image job may take to complete.
503
503
  *
504
+ * @property {Number} [tileRetryMax=0]
505
+ * The max number of retries when a tile download fails. By default it's 0, so retries are disabled.
506
+ *
507
+ * @property {Number} [tileRetryDelay=2500]
508
+ * Milliseconds to wait after each tile retry if tileRetryMax is set.
509
+ *
504
510
  * @property {Boolean} [useCanvas=true]
505
511
  * Set to false to not use an HTML canvas element for image rendering even if canvas is supported.
506
512
  *
@@ -565,50 +571,50 @@
565
571
  * viewing the first image and the 'next' button will wrap to the first
566
572
  * image when viewing the last image.
567
573
  *
568
- * @property {String} zoomInButton
569
- * Set the id of the custom 'Zoom in' button to use.
574
+ *@property {String|Element} zoomInButton
575
+ * Set the id or element of the custom 'Zoom in' button to use.
570
576
  * This is useful to have a custom button anywhere in the web page.<br>
571
577
  * To only change the button images, consider using
572
578
  * {@link OpenSeadragon.Options.navImages}
573
579
  *
574
- * @property {String} zoomOutButton
575
- * Set the id of the custom 'Zoom out' button to use.
580
+ * @property {String|Element} zoomOutButton
581
+ * Set the id or element of the custom 'Zoom out' button to use.
576
582
  * This is useful to have a custom button anywhere in the web page.<br>
577
583
  * To only change the button images, consider using
578
584
  * {@link OpenSeadragon.Options.navImages}
579
585
  *
580
- * @property {String} homeButton
581
- * Set the id of the custom 'Go home' button to use.
586
+ * @property {String|Element} homeButton
587
+ * Set the id or element of the custom 'Go home' button to use.
582
588
  * This is useful to have a custom button anywhere in the web page.<br>
583
589
  * To only change the button images, consider using
584
590
  * {@link OpenSeadragon.Options.navImages}
585
591
  *
586
- * @property {String} fullPageButton
587
- * Set the id of the custom 'Toggle full page' button to use.
592
+ * @property {String|Element} fullPageButton
593
+ * Set the id or element of the custom 'Toggle full page' button to use.
588
594
  * This is useful to have a custom button anywhere in the web page.<br>
589
595
  * To only change the button images, consider using
590
596
  * {@link OpenSeadragon.Options.navImages}
591
597
  *
592
- * @property {String} rotateLeftButton
593
- * Set the id of the custom 'Rotate left' button to use.
598
+ * @property {String|Element} rotateLeftButton
599
+ * Set the id or element of the custom 'Rotate left' button to use.
594
600
  * This is useful to have a custom button anywhere in the web page.<br>
595
601
  * To only change the button images, consider using
596
602
  * {@link OpenSeadragon.Options.navImages}
597
603
  *
598
- * @property {String} rotateRightButton
599
- * Set the id of the custom 'Rotate right' button to use.
604
+ * @property {String|Element} rotateRightButton
605
+ * Set the id or element of the custom 'Rotate right' button to use.
600
606
  * This is useful to have a custom button anywhere in the web page.<br>
601
607
  * To only change the button images, consider using
602
608
  * {@link OpenSeadragon.Options.navImages}
603
609
  *
604
- * @property {String} previousButton
605
- * Set the id of the custom 'Previous page' button to use.
610
+ * @property {String|Element} previousButton
611
+ * Set the id or element of the custom 'Previous page' button to use.
606
612
  * This is useful to have a custom button anywhere in the web page.<br>
607
613
  * To only change the button images, consider using
608
614
  * {@link OpenSeadragon.Options.navImages}
609
615
  *
610
- * @property {String} nextButton
611
- * Set the id of the custom 'Next page' button to use.
616
+ * @property {String|Element} nextButton
617
+ * Set the id or element of the custom 'Next page' button to use.
612
618
  * This is useful to have a custom button anywhere in the web page.<br>
613
619
  * To only change the button images, consider using
614
620
  * {@link OpenSeadragon.Options.navImages}
@@ -819,9 +825,9 @@ function OpenSeadragon( options ){
819
825
  * @since 1.0.0
820
826
  */
821
827
  $.version = {
822
- versionStr: '4.0.0',
828
+ versionStr: '4.1.0',
823
829
  major: parseInt('4', 10),
824
- minor: parseInt('0', 10),
830
+ minor: parseInt('1', 10),
825
831
  revision: parseInt('0', 10)
826
832
  };
827
833
 
@@ -832,14 +838,16 @@ function OpenSeadragon( options ){
832
838
  * @private
833
839
  */
834
840
  var class2type = {
835
- '[object Boolean]': 'boolean',
836
- '[object Number]': 'number',
837
- '[object String]': 'string',
838
- '[object Function]': 'function',
839
- '[object Array]': 'array',
840
- '[object Date]': 'date',
841
- '[object RegExp]': 'regexp',
842
- '[object Object]': 'object'
841
+ '[object Boolean]': 'boolean',
842
+ '[object Number]': 'number',
843
+ '[object String]': 'string',
844
+ '[object Function]': 'function',
845
+ '[object AsyncFunction]': 'function',
846
+ '[object Promise]': 'promise',
847
+ '[object Array]': 'array',
848
+ '[object Date]': 'date',
849
+ '[object RegExp]': 'regexp',
850
+ '[object Object]': 'object'
843
851
  },
844
852
  // Save a reference to some core methods
845
853
  toString = Object.prototype.toString,
@@ -855,7 +863,6 @@ function OpenSeadragon( options ){
855
863
  return $.type(obj) === "function";
856
864
  };
857
865
 
858
-
859
866
  /**
860
867
  * Taken from jQuery 1.6.1
861
868
  * @function isArray
@@ -1360,6 +1367,8 @@ function OpenSeadragon( options ){
1360
1367
  maxImageCacheCount: 200,
1361
1368
  timeout: 30000,
1362
1369
  useCanvas: true, // Use canvas element for drawing if available
1370
+ tileRetryMax: 0,
1371
+ tileRetryDelay: 2500,
1363
1372
 
1364
1373
  //INTERFACE RESOURCE SETTINGS
1365
1374
  prefixUrl: "/images/",
@@ -3109,7 +3118,7 @@ $.EventSource.prototype = {
3109
3118
 
3110
3119
  /**
3111
3120
  * Add an event handler to be triggered only once (or a given number of times)
3112
- * for a given event.
3121
+ * for a given event. It is not removable with removeHandler().
3113
3122
  * @function
3114
3123
  * @param {String} eventName - Name of event to register.
3115
3124
  * @param {OpenSeadragon.EventHandler} handler - Function to call when event
@@ -3118,8 +3127,9 @@ $.EventSource.prototype = {
3118
3127
  * to the handler.
3119
3128
  * @param {Number} [times=1] - The number of times to handle the event
3120
3129
  * before removing it.
3130
+ * @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority.
3121
3131
  */
3122
- addOnceHandler: function(eventName, handler, userData, times) {
3132
+ addOnceHandler: function(eventName, handler, userData, times, priority) {
3123
3133
  var self = this;
3124
3134
  times = times || 1;
3125
3135
  var count = 0;
@@ -3128,9 +3138,9 @@ $.EventSource.prototype = {
3128
3138
  if (count === times) {
3129
3139
  self.removeHandler(eventName, onceHandler);
3130
3140
  }
3131
- handler(event);
3141
+ return handler(event);
3132
3142
  };
3133
- this.addHandler(eventName, onceHandler, userData);
3143
+ this.addHandler(eventName, onceHandler, userData, priority);
3134
3144
  },
3135
3145
 
3136
3146
  /**
@@ -3139,14 +3149,22 @@ $.EventSource.prototype = {
3139
3149
  * @param {String} eventName - Name of event to register.
3140
3150
  * @param {OpenSeadragon.EventHandler} handler - Function to call when event is triggered.
3141
3151
  * @param {Object} [userData=null] - Arbitrary object to be passed unchanged to the handler.
3152
+ * @param {Number} [priority=0] - Handler priority. By default, all priorities are 0. Higher number = priority.
3142
3153
  */
3143
- addHandler: function ( eventName, handler, userData ) {
3154
+ addHandler: function ( eventName, handler, userData, priority ) {
3144
3155
  var events = this.events[ eventName ];
3145
3156
  if ( !events ) {
3146
3157
  this.events[ eventName ] = events = [];
3147
3158
  }
3148
3159
  if ( handler && $.isFunction( handler ) ) {
3149
- events[ events.length ] = { handler: handler, userData: userData || null };
3160
+ var index = events.length,
3161
+ event = { handler: handler, userData: userData || null, priority: priority || 0 };
3162
+ events[ index ] = event;
3163
+ while ( index > 0 && events[ index - 1 ].priority < events[ index ].priority ) {
3164
+ events[ index ] = events[ index - 1 ];
3165
+ events[ index - 1 ] = event;
3166
+ index--;
3167
+ }
3150
3168
  }
3151
3169
  },
3152
3170
 
@@ -3207,7 +3225,7 @@ $.EventSource.prototype = {
3207
3225
  * @function
3208
3226
  * @param {String} eventName - Name of event to get handlers for.
3209
3227
  */
3210
- getHandler: function ( eventName ) {
3228
+ getHandler: function ( eventName) {
3211
3229
  var events = this.events[ eventName ];
3212
3230
  if ( !events || !events.length ) {
3213
3231
  return null;
@@ -3237,15 +3255,12 @@ $.EventSource.prototype = {
3237
3255
  raiseEvent: function( eventName, eventArgs ) {
3238
3256
  //uncomment if you want to get a log of all events
3239
3257
  //$.console.log( eventName );
3240
- var handler = this.getHandler( eventName );
3241
3258
 
3259
+ var handler = this.getHandler( eventName );
3242
3260
  if ( handler ) {
3243
- if ( !eventArgs ) {
3244
- eventArgs = {};
3245
- }
3246
-
3247
- handler( this, eventArgs );
3261
+ return handler( this, eventArgs || {} );
3248
3262
  }
3263
+ return undefined;
3249
3264
  }
3250
3265
  };
3251
3266
 
@@ -7911,7 +7926,9 @@ $.Viewer = function( options ) {
7911
7926
  nonPrimaryPressHandler: $.delegate( this, onCanvasNonPrimaryPress ),
7912
7927
  nonPrimaryReleaseHandler: $.delegate( this, onCanvasNonPrimaryRelease ),
7913
7928
  scrollHandler: $.delegate( this, onCanvasScroll ),
7914
- pinchHandler: $.delegate( this, onCanvasPinch )
7929
+ pinchHandler: $.delegate( this, onCanvasPinch ),
7930
+ focusHandler: $.delegate( this, onCanvasFocus ),
7931
+ blurHandler: $.delegate( this, onCanvasBlur ),
7915
7932
  });
7916
7933
 
7917
7934
  this.outerTracker = new $.MouseTracker({
@@ -8010,7 +8027,9 @@ $.Viewer = function( options ) {
8010
8027
  // Create the image loader
8011
8028
  this.imageLoader = new $.ImageLoader({
8012
8029
  jobLimit: this.imageLoaderLimit,
8013
- timeout: options.timeout
8030
+ timeout: options.timeout,
8031
+ tileRetryMax: this.tileRetryMax,
8032
+ tileRetryDelay: this.tileRetryDelay
8014
8033
  });
8015
8034
 
8016
8035
  // Create the tile cache
@@ -8566,7 +8585,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8566
8585
  * Turns debugging mode on or off for this viewer.
8567
8586
  *
8568
8587
  * @function
8569
- * @param {Boolean} true to turn debug on, false to turn debug off.
8588
+ * @param {Boolean} debugMode true to turn debug on, false to turn debug off.
8570
8589
  */
8571
8590
  setDebugMode: function(debugMode){
8572
8591
 
@@ -8578,6 +8597,63 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8578
8597
  this.forceRedraw();
8579
8598
  },
8580
8599
 
8600
+ /**
8601
+ * Update headers to include when making AJAX requests.
8602
+ *
8603
+ * Unless `propagate` is set to false (which is likely only useful in rare circumstances),
8604
+ * the updated headers are propagated to all tiled images, each of which will subsequently
8605
+ * propagate the changed headers to all their tiles.
8606
+ * If applicable, the headers of the viewer's navigator and reference strip will also be updated.
8607
+ *
8608
+ * Note that the rules for merging headers still apply, i.e. headers returned by
8609
+ * {@link OpenSeadragon.TileSource#getTileAjaxHeaders} take precedence over
8610
+ * `TiledImage.ajaxHeaders`, which take precedence over the headers here in the viewer.
8611
+ *
8612
+ * @function
8613
+ * @param {Object} ajaxHeaders Updated AJAX headers.
8614
+ * @param {Boolean} [propagate=true] Whether to propagate updated headers to tiled images, etc.
8615
+ */
8616
+ setAjaxHeaders: function(ajaxHeaders, propagate) {
8617
+ if (ajaxHeaders === null) {
8618
+ ajaxHeaders = {};
8619
+ }
8620
+ if (!$.isPlainObject(ajaxHeaders)) {
8621
+ console.error('[Viewer.setAjaxHeaders] Ignoring invalid headers, must be a plain object');
8622
+ return;
8623
+ }
8624
+ if (propagate === undefined) {
8625
+ propagate = true;
8626
+ }
8627
+
8628
+ this.ajaxHeaders = ajaxHeaders;
8629
+
8630
+ if (propagate) {
8631
+ for (var i = 0; i < this.world.getItemCount(); i++) {
8632
+ this.world.getItemAt(i)._updateAjaxHeaders(true);
8633
+ }
8634
+
8635
+ if (this.navigator) {
8636
+ this.navigator.setAjaxHeaders(this.ajaxHeaders, true);
8637
+ }
8638
+
8639
+ if (this.referenceStrip && this.referenceStrip.miniViewers) {
8640
+ for (var key in this.referenceStrip.miniViewers) {
8641
+ this.referenceStrip.miniViewers[key].setAjaxHeaders(this.ajaxHeaders, true);
8642
+ }
8643
+ }
8644
+ }
8645
+ },
8646
+
8647
+ /**
8648
+ * Adds the given button to this viewer.
8649
+ *
8650
+ * @function
8651
+ * @param {OpenSeadragon.Button} button
8652
+ */
8653
+ addButton: function( button ){
8654
+ this.buttonGroup.addButton(button);
8655
+ },
8656
+
8581
8657
  /**
8582
8658
  * @function
8583
8659
  * @returns {Boolean}
@@ -8992,7 +9068,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8992
9068
  * A set of headers to include when making tile AJAX requests.
8993
9069
  * Note that these headers will be merged over any headers specified in {@link OpenSeadragon.Options}.
8994
9070
  * Specifying a falsy value for a header will clear its existing value set at the Viewer level (if any).
8995
- * requests.
8996
9071
  * @param {Function} [options.success] A function that gets called when the image is
8997
9072
  * successfully added. It's passed the event object which contains a single property:
8998
9073
  * "item", which is the resulting instance of TiledImage.
@@ -9040,10 +9115,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
9040
9115
  if (options.loadTilesWithAjax === undefined) {
9041
9116
  options.loadTilesWithAjax = this.loadTilesWithAjax;
9042
9117
  }
9043
- if (options.ajaxHeaders === undefined || options.ajaxHeaders === null) {
9044
- options.ajaxHeaders = this.ajaxHeaders;
9045
- } else if ($.isPlainObject(options.ajaxHeaders) && $.isPlainObject(this.ajaxHeaders)) {
9046
- options.ajaxHeaders = $.extend({}, this.ajaxHeaders, options.ajaxHeaders);
9118
+ if (!$.isPlainObject(options.ajaxHeaders)) {
9119
+ options.ajaxHeaders = {};
9047
9120
  }
9048
9121
 
9049
9122
  var myQueueItem = {
@@ -10359,7 +10432,7 @@ function onCanvasKeyDown( event ) {
10359
10432
 
10360
10433
  if ( !canvasKeyDownEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
10361
10434
  switch( event.keyCode ){
10362
- case 38://up arrow
10435
+ case 38://up arrow/shift uparrow
10363
10436
  if (!canvasKeyDownEventArgs.preventVerticalPan) {
10364
10437
  if ( event.shift ) {
10365
10438
  this.viewport.zoomBy(1.1);
@@ -10370,7 +10443,7 @@ function onCanvasKeyDown( event ) {
10370
10443
  }
10371
10444
  event.preventDefault = true;
10372
10445
  break;
10373
- case 40://down arrow
10446
+ case 40://down arrow/shift downarrow
10374
10447
  if (!canvasKeyDownEventArgs.preventVerticalPan) {
10375
10448
  if ( event.shift ) {
10376
10449
  this.viewport.zoomBy(0.9);
@@ -10395,35 +10468,12 @@ function onCanvasKeyDown( event ) {
10395
10468
  }
10396
10469
  event.preventDefault = true;
10397
10470
  break;
10398
- default:
10399
- //console.log( 'navigator keycode %s', event.keyCode );
10400
- event.preventDefault = false;
10401
- break;
10402
- }
10403
- } else {
10404
- event.preventDefault = false;
10405
- }
10406
- }
10407
- function onCanvasKeyPress( event ) {
10408
- var canvasKeyPressEventArgs = {
10409
- originalEvent: event.originalEvent,
10410
- preventDefaultAction: false,
10411
- preventVerticalPan: event.preventVerticalPan || !this.panVertical,
10412
- preventHorizontalPan: event.preventHorizontalPan || !this.panHorizontal
10413
- };
10414
-
10415
- // This event is documented in onCanvasKeyDown
10416
- this.raiseEvent('canvas-key', canvasKeyPressEventArgs);
10417
-
10418
- if ( !canvasKeyPressEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
10419
- switch( event.keyCode ){
10420
- case 43://=|+
10421
- case 61://=|+
10471
+ case 187://=|+
10422
10472
  this.viewport.zoomBy(1.1);
10423
10473
  this.viewport.applyConstraints();
10424
10474
  event.preventDefault = true;
10425
10475
  break;
10426
- case 45://-|_
10476
+ case 189://-|_
10427
10477
  this.viewport.zoomBy(0.9);
10428
10478
  this.viewport.applyConstraints();
10429
10479
  event.preventDefault = true;
@@ -10433,74 +10483,71 @@ function onCanvasKeyPress( event ) {
10433
10483
  this.viewport.applyConstraints();
10434
10484
  event.preventDefault = true;
10435
10485
  break;
10436
- case 119://w
10437
- case 87://W
10438
- if (!canvasKeyPressEventArgs.preventVerticalPan) {
10486
+ case 87://W/w
10487
+ if (!canvasKeyDownEventArgs.preventVerticalPan) {
10439
10488
  if ( event.shift ) {
10440
10489
  this.viewport.zoomBy(1.1);
10441
10490
  } else {
10442
10491
  this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
10443
10492
  }
10444
10493
  this.viewport.applyConstraints();
10445
- }
10446
- event.preventDefault = true;
10447
- break;
10448
- case 115://s
10449
- case 83://S
10450
- if (!canvasKeyPressEventArgs.preventVerticalPan) {
10451
- if ( event.shift ) {
10452
- this.viewport.zoomBy(0.9);
10453
- } else {
10454
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
10455
- }
10456
- this.viewport.applyConstraints();
10457
10494
  }
10458
10495
  event.preventDefault = true;
10459
10496
  break;
10460
- case 97://a
10461
- if (!canvasKeyPressEventArgs.preventHorizontalPan) {
10497
+ case 83://S/s
10498
+ if (!canvasKeyDownEventArgs.preventVerticalPan) {
10499
+ if ( event.shift ) {
10500
+ this.viewport.zoomBy(0.9);
10501
+ } else {
10502
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
10503
+ }
10504
+ this.viewport.applyConstraints();
10505
+ }
10506
+ event.preventDefault = true;
10507
+ break;
10508
+ case 65://a/A
10509
+ if (!canvasKeyDownEventArgs.preventHorizontalPan) {
10462
10510
  this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
10463
10511
  this.viewport.applyConstraints();
10464
10512
  }
10465
10513
  event.preventDefault = true;
10466
10514
  break;
10467
- case 100://d
10468
- if (!canvasKeyPressEventArgs.preventHorizontalPan) {
10469
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
10470
- this.viewport.applyConstraints();
10515
+ case 68://d/D
10516
+ if (!canvasKeyDownEventArgs.preventHorizontalPan) {
10517
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
10518
+ this.viewport.applyConstraints();
10519
+ }
10520
+ event.preventDefault = true;
10521
+ break;
10522
+ case 82: //r - clockwise rotation/R - counterclockwise rotation
10523
+ if(event.shift){
10524
+ if(this.viewport.flipped){
10525
+ this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
10526
+ } else{
10527
+ this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
10528
+ }
10529
+ }else{
10530
+ if(this.viewport.flipped){
10531
+ this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
10532
+ } else{
10533
+ this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
10534
+ }
10471
10535
  }
10536
+ this.viewport.applyConstraints();
10537
+ event.preventDefault = true;
10538
+ break;
10539
+ case 70: //f/F
10540
+ this.viewport.toggleFlip();
10472
10541
  event.preventDefault = true;
10473
10542
  break;
10474
- case 114: //r - clockwise rotation
10475
- if(this.viewport.flipped){
10476
- this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
10477
- } else{
10478
- this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
10479
- }
10480
- this.viewport.applyConstraints();
10481
- event.preventDefault = true;
10482
- break;
10483
- case 82: //R - counterclockwise rotation
10484
- if(this.viewport.flipped){
10485
- this.viewport.setRotation(this.viewport.getRotation() + this.rotationIncrement);
10486
- } else{
10487
- this.viewport.setRotation(this.viewport.getRotation() - this.rotationIncrement);
10488
- }
10489
- this.viewport.applyConstraints();
10490
- event.preventDefault = true;
10491
- break;
10492
- case 102: //f
10493
- this.viewport.toggleFlip();
10494
- event.preventDefault = true;
10495
- break;
10496
- case 106: //j - previous image source
10497
- this.goToPreviousPage();
10498
- break;
10499
- case 107: //k - next image source
10500
- this.goToNextPage();
10501
- break;
10543
+ case 74: //j - previous image source
10544
+ this.goToPreviousPage();
10545
+ break;
10546
+ case 75: //k - next image source
10547
+ this.goToNextPage();
10548
+ break;
10502
10549
  default:
10503
- // console.log( 'navigator keycode %s', event.keyCode );
10550
+ //console.log( 'navigator keycode %s', event.keyCode );
10504
10551
  event.preventDefault = false;
10505
10552
  break;
10506
10553
  }
@@ -10509,7 +10556,24 @@ function onCanvasKeyPress( event ) {
10509
10556
  }
10510
10557
  }
10511
10558
 
10559
+ function onCanvasKeyPress( event ) {
10560
+ var canvasKeyPressEventArgs = {
10561
+ originalEvent: event.originalEvent,
10562
+ };
10563
+
10564
+ /**
10565
+ * Raised when a keyboard key is pressed and the focus is on the {@link OpenSeadragon.Viewer#canvas} element.
10566
+ *
10567
+ * @event canvas-key-press
10568
+ * @memberof OpenSeadragon.Viewer
10569
+ * @type {object}
10570
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
10571
+ * @property {Object} originalEvent - The original DOM event.
10572
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
10573
+ */
10512
10574
 
10575
+ this.raiseEvent('canvas-key-press', canvasKeyPressEventArgs);
10576
+ }
10513
10577
 
10514
10578
  function onCanvasClick( event ) {
10515
10579
  var gestureSettings;
@@ -11021,11 +11085,49 @@ function onCanvasPinch( event ) {
11021
11085
  event.gesturePoints[0].currentPos.x - event.gesturePoints[1].currentPos.x);
11022
11086
  var angle2 = Math.atan2(event.gesturePoints[0].lastPos.y - event.gesturePoints[1].lastPos.y,
11023
11087
  event.gesturePoints[0].lastPos.x - event.gesturePoints[1].lastPos.x);
11024
- this.viewport.setRotation(this.viewport.getRotation() + ((angle1 - angle2) * (180 / Math.PI)));
11088
+ centerPt = this.viewport.pointFromPixel( event.center, true );
11089
+ this.viewport.rotateTo(this.viewport.getRotation(true) + ((angle1 - angle2) * (180 / Math.PI)), centerPt, true);
11025
11090
  }
11026
11091
  }
11027
11092
  }
11028
11093
 
11094
+ function onCanvasFocus( event ) {
11095
+
11096
+ /**
11097
+ * Raised when the {@link OpenSeadragon.Viewer#canvas} element gets keyboard focus.
11098
+ *
11099
+ * @event canvas-focus
11100
+ * @memberof OpenSeadragon.Viewer
11101
+ * @type {object}
11102
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
11103
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
11104
+ * @property {Object} originalEvent - The original DOM event.
11105
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
11106
+ */
11107
+ this.raiseEvent( 'canvas-focus', {
11108
+ tracker: event.eventSource,
11109
+ originalEvent: event.originalEvent
11110
+ });
11111
+ }
11112
+
11113
+ function onCanvasBlur( event ) {
11114
+ /**
11115
+ * Raised when the {@link OpenSeadragon.Viewer#canvas} element loses keyboard focus.
11116
+ *
11117
+ * @event canvas-blur
11118
+ * @memberof OpenSeadragon.Viewer
11119
+ * @type {object}
11120
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
11121
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
11122
+ * @property {Object} originalEvent - The original DOM event.
11123
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
11124
+ */
11125
+ this.raiseEvent( 'canvas-blur', {
11126
+ tracker: event.eventSource,
11127
+ originalEvent: event.originalEvent
11128
+ });
11129
+ }
11130
+
11029
11131
  function onCanvasScroll( event ) {
11030
11132
  var canvasScrollEventArgs,
11031
11133
  gestureSettings,
@@ -11653,6 +11755,7 @@ $.Navigator = function( options ){
11653
11755
  style.styleFloat = 'left'; //IE
11654
11756
  style.zIndex = 999999999;
11655
11757
  style.cursor = 'default';
11758
+ style.boxSizing = 'content-box';
11656
11759
  }( this.displayRegion.style, this.borderWidth ));
11657
11760
  $.setElementPointerEventsNone( this.displayRegion );
11658
11761
  $.setElementTouchActionNone( this.displayRegion );
@@ -11692,19 +11795,19 @@ $.Navigator = function( options ){
11692
11795
  this.displayRegionContainer.appendChild(this.displayRegion);
11693
11796
  this.element.getElementsByTagName('div')[0].appendChild(this.displayRegionContainer);
11694
11797
 
11695
- function rotate(degrees) {
11798
+ function rotate(degrees, immediately) {
11696
11799
  _setTransformRotate(_this.displayRegionContainer, degrees);
11697
11800
  _setTransformRotate(_this.displayRegion, -degrees);
11698
- _this.viewport.setRotation(degrees);
11801
+ _this.viewport.setRotation(degrees, immediately);
11699
11802
  }
11700
11803
  if (options.navigatorRotate) {
11701
11804
  var degrees = options.viewer.viewport ?
11702
11805
  options.viewer.viewport.getRotation() :
11703
11806
  options.viewer.degrees || 0;
11704
11807
 
11705
- rotate(degrees);
11808
+ rotate(degrees, true);
11706
11809
  options.viewer.addHandler("rotate", function (args) {
11707
- rotate(args.degrees);
11810
+ rotate(args.degrees, args.immediately);
11708
11811
  });
11709
11812
  }
11710
11813
 
@@ -11792,6 +11895,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
11792
11895
  this.width = width;
11793
11896
  this.element.style.width = typeof (width) === "number" ? (width + 'px') : width;
11794
11897
  this._resizeWithViewer = false;
11898
+ this.updateSize();
11795
11899
  },
11796
11900
 
11797
11901
  /**
@@ -11802,6 +11906,7 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
11802
11906
  this.height = height;
11803
11907
  this.element.style.height = typeof (height) === "number" ? (height + 'px') : height;
11804
11908
  this._resizeWithViewer = false;
11909
+ this.updateSize();
11805
11910
  },
11806
11911
 
11807
11912
  /**
@@ -11863,15 +11968,20 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
11863
11968
  bottomright = this.viewport.pixelFromPointNoRotate(bounds.getBottomRight(), false)
11864
11969
  .minus( this.totalBorderWidths );
11865
11970
 
11971
+ if (!this.navigatorRotate) {
11972
+ var degrees = viewport.getRotation(true);
11973
+ _setTransformRotate(this.displayRegion, -degrees);
11974
+ }
11975
+
11866
11976
  //update style for navigator-box
11867
11977
  var style = this.displayRegion.style;
11868
11978
  style.display = this.world.getItemCount() ? 'block' : 'none';
11869
11979
 
11870
- style.top = Math.round( topleft.y ) + 'px';
11871
- style.left = Math.round( topleft.x ) + 'px';
11980
+ style.top = topleft.y.toFixed(2) + "px";
11981
+ style.left = topleft.x.toFixed(2) + "px";
11872
11982
 
11873
- var width = Math.abs( topleft.x - bottomright.x );
11874
- var height = Math.abs( topleft.y - bottomright.y );
11983
+ var width = bottomright.x - topleft.x;
11984
+ var height = bottomright.y - topleft.y;
11875
11985
  // make sure width and height are non-negative so IE doesn't throw
11876
11986
  style.width = Math.round( Math.max( width, 0 ) ) + 'px';
11877
11987
  style.height = Math.round( Math.max( height, 0 ) ) + 'px';
@@ -13165,6 +13275,11 @@ $.TileSource.prototype = {
13165
13275
  * The headers returned here will override headers specified at the Viewer or TiledImage level.
13166
13276
  * Specifying a falsy value for a header will clear its existing value set at the Viewer or
13167
13277
  * TiledImage level (if any).
13278
+ *
13279
+ * Note that the headers of existing tiles don't automatically change when this function
13280
+ * returns updated headers. To do that, you need to call {@link OpenSeadragon.Viewer#setAjaxHeaders}
13281
+ * and propagate the changes.
13282
+ *
13168
13283
  * @function
13169
13284
  * @param {Number} level
13170
13285
  * @param {Number} x
@@ -13434,7 +13549,7 @@ function processResponse( xhr ){
13434
13549
  throw new Error( $.getString( "Errors.Status", status, statusText ) );
13435
13550
  }
13436
13551
 
13437
- if( responseText.match(/\s*<.*/) ){
13552
+ if( responseText.match(/^\s*<.*/) ){
13438
13553
  try{
13439
13554
  data = ( xhr.responseXML && xhr.responseXML.documentElement ) ?
13440
13555
  xhr.responseXML :
@@ -13868,7 +13983,7 @@ function configureFromObject( tileSource, configuration ){
13868
13983
  * OpenSeadragon - IIIFTileSource
13869
13984
  *
13870
13985
  * Copyright (C) 2009 CodePlex Foundation
13871
- * Copyright (C) 2010-2022 OpenSeadragon contributors
13986
+ * Copyright (C) 2010-2023 OpenSeadragon contributors
13872
13987
  *
13873
13988
  * Redistribution and use in source and binary forms, with or without
13874
13989
  * modification, are permitted provided that the following conditions are
@@ -14007,6 +14122,18 @@ $.IIIFTileSource = function( options ){
14007
14122
  }
14008
14123
  }
14009
14124
 
14125
+ // Create an array with our exact resolution sizes if these have been supplied
14126
+ if( this.sizes ) {
14127
+ var sizeLength = this.sizes.length;
14128
+ if ( (sizeLength === options.maxLevel) || (sizeLength === options.maxLevel + 1) ) {
14129
+ this.levelSizes = this.sizes;
14130
+ // Need to take into account that the list may or may not include the full resolution size
14131
+ if( sizeLength === options.maxLevel ) {
14132
+ this.levelSizes.push( {width: this.width, height: this.height} );
14133
+ }
14134
+ }
14135
+ }
14136
+
14010
14137
  $.TileSource.apply( this, [ options ] );
14011
14138
  };
14012
14139
 
@@ -14199,7 +14326,17 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
14199
14326
  }
14200
14327
  }
14201
14328
 
14202
- return $.TileSource.prototype.getNumTiles.call(this, level);
14329
+ // Use supplied list of scaled resolution sizes if these exist
14330
+ if( this.levelSizes ) {
14331
+ var levelSize = this.levelSizes[level];
14332
+ var x = Math.ceil( levelSize.width / this.getTileWidth(level) ),
14333
+ y = Math.ceil( levelSize.height / this.getTileHeight(level) );
14334
+ return new $.Point( x, y );
14335
+ }
14336
+ // Otherwise call default TileSource->getNumTiles() function
14337
+ else {
14338
+ return $.TileSource.prototype.getNumTiles.call(this, level);
14339
+ }
14203
14340
  },
14204
14341
 
14205
14342
 
@@ -14214,6 +14351,34 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
14214
14351
  return new $.Point(0, 0);
14215
14352
  }
14216
14353
 
14354
+ // Use supplied list of scaled resolution sizes if these exist
14355
+ if( this.levelSizes ) {
14356
+
14357
+ var validPoint = point.x >= 0 && point.x <= 1 &&
14358
+ point.y >= 0 && point.y <= 1 / this.aspectRatio;
14359
+ $.console.assert(validPoint, "[TileSource.getTileAtPoint] must be called with a valid point.");
14360
+
14361
+ var widthScaled = this.levelSizes[level].width;
14362
+ var pixelX = point.x * widthScaled;
14363
+ var pixelY = point.y * widthScaled;
14364
+
14365
+ var x = Math.floor(pixelX / this.getTileWidth(level));
14366
+ var y = Math.floor(pixelY / this.getTileHeight(level));
14367
+
14368
+ // When point.x == 1 or point.y == 1 / this.aspectRatio we want to
14369
+ // return the last tile of the row/column
14370
+ if (point.x >= 1) {
14371
+ x = this.getNumTiles(level).x - 1;
14372
+ }
14373
+ var EPSILON = 1e-15;
14374
+ if (point.y >= 1 / this.aspectRatio - EPSILON) {
14375
+ y = this.getNumTiles(level).y - 1;
14376
+ }
14377
+
14378
+ return new $.Point(x, y);
14379
+ }
14380
+
14381
+ // Otherwise call default TileSource->getTileAtPoint() function
14217
14382
  return $.TileSource.prototype.getTileAtPoint.call(this, level, point);
14218
14383
  },
14219
14384
 
@@ -14241,10 +14406,9 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
14241
14406
  var IIIF_ROTATION = '0',
14242
14407
  //## get the scale (level as a decimal)
14243
14408
  scale = Math.pow( 0.5, this.maxLevel - level ),
14244
-
14245
14409
  //# image dimensions at this level
14246
- levelWidth = Math.round( this.width * scale ),
14247
- levelHeight = Math.round( this.height * scale ),
14410
+ levelWidth,
14411
+ levelHeight,
14248
14412
 
14249
14413
  //## iiif region
14250
14414
  tileWidth,
@@ -14262,6 +14426,17 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
14262
14426
  iiifQuality,
14263
14427
  uri;
14264
14428
 
14429
+ // Use supplied list of scaled resolution sizes if these exist
14430
+ if( this.levelSizes ) {
14431
+ levelWidth = this.levelSizes[level].width;
14432
+ levelHeight = this.levelSizes[level].height;
14433
+ }
14434
+ // Otherwise calculate the sizes ourselves
14435
+ else {
14436
+ levelWidth = Math.ceil( this.width * scale );
14437
+ levelHeight = Math.ceil( this.height * scale );
14438
+ }
14439
+
14265
14440
  tileWidth = this.getTileWidth(level);
14266
14441
  tileHeight = this.getTileHeight(level);
14267
14442
  iiifTileSizeWidth = Math.round( tileWidth / scale );
@@ -14292,8 +14467,8 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
14292
14467
  } else {
14293
14468
  iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
14294
14469
  }
14295
- iiifSizeW = Math.round( iiifTileW * scale );
14296
- iiifSizeH = Math.round( iiifTileH * scale );
14470
+ iiifSizeW = Math.min( tileWidth, levelWidth - (x * tileWidth) );
14471
+ iiifSizeH = Math.min( tileHeight, levelHeight - (y * tileHeight) );
14297
14472
  if ( this.version === 2 && iiifSizeW === this.width ) {
14298
14473
  iiifSize = "full";
14299
14474
  } else if ( this.version === 3 && iiifSizeW === this.width && iiifSizeH === this.height ) {
@@ -16135,6 +16310,17 @@ $.ButtonGroup = function( options ) {
16135
16310
  /** @lends OpenSeadragon.ButtonGroup.prototype */
16136
16311
  $.ButtonGroup.prototype = {
16137
16312
 
16313
+ /**
16314
+ * Adds the given button to this button group.
16315
+ *
16316
+ * @function
16317
+ * @param {OpenSeadragon.Button} button
16318
+ */
16319
+ addButton: function( button ){
16320
+ this.buttons.push(button);
16321
+ this.element.appendChild(button.element);
16322
+ },
16323
+
16138
16324
  /**
16139
16325
  * TODO: Figure out why this is used on the public API and if a more useful
16140
16326
  * api can be created.
@@ -17035,7 +17221,8 @@ function onStripClick( event ) {
17035
17221
  var page;
17036
17222
 
17037
17223
  if ( 'horizontal' === this.scroll ) {
17038
- page = Math.floor(event.position.x / this.panelWidth);
17224
+ // +4px fix to solve problem with precision on thumbnail selection if there is a lot of them
17225
+ page = Math.floor(event.position.x / (this.panelWidth + 4));
17039
17226
  } else {
17040
17227
  page = Math.floor(event.position.y / this.panelHeight);
17041
17228
  }
@@ -17729,17 +17916,19 @@ function transform( stiffness, x ) {
17729
17916
  * @param {Function} [options.callback] - Called once image has been downloaded.
17730
17917
  * @param {Function} [options.abort] - Called when this image job is aborted.
17731
17918
  * @param {Number} [options.timeout] - The max number of milliseconds that this image job may take to complete.
17919
+ * @param {Number} [options.tries] - Actual number of the current try.
17732
17920
  */
17733
17921
  $.ImageJob = function(options) {
17734
17922
 
17735
17923
  $.extend(true, this, {
17736
17924
  timeout: $.DEFAULT_SETTINGS.timeout,
17737
- jobId: null
17925
+ jobId: null,
17926
+ tries: 0
17738
17927
  }, options);
17739
17928
 
17740
17929
  /**
17741
17930
  * Data object which will contain downloaded image data.
17742
- * @member {Image|*} image data object, by default an Image object (depends on TileSource)
17931
+ * @member {Image|*} data data object, by default an Image object (depends on TileSource)
17743
17932
  * @memberof OpenSeadragon.ImageJob#
17744
17933
  */
17745
17934
  this.data = null;
@@ -17766,6 +17955,8 @@ $.ImageJob.prototype = {
17766
17955
  * @method
17767
17956
  */
17768
17957
  start: function() {
17958
+ this.tries++;
17959
+
17769
17960
  var self = this;
17770
17961
  var selfAbort = this.abort;
17771
17962
 
@@ -17817,6 +18008,7 @@ $.ImageLoader = function(options) {
17817
18008
  jobLimit: $.DEFAULT_SETTINGS.imageLoaderLimit,
17818
18009
  timeout: $.DEFAULT_SETTINGS.timeout,
17819
18010
  jobQueue: [],
18011
+ failedTiles: [],
17820
18012
  jobsInProgress: 0
17821
18013
  }, options);
17822
18014
 
@@ -17899,7 +18091,8 @@ $.ImageLoader.prototype = {
17899
18091
  };
17900
18092
 
17901
18093
  /**
17902
- * Cleans up ImageJob once completed.
18094
+ * Cleans up ImageJob once completed. Restarts job after tileRetryDelay seconds if failed
18095
+ * but max tileRetryMax times
17903
18096
  * @method
17904
18097
  * @private
17905
18098
  * @param loader - ImageLoader used to start job.
@@ -17907,6 +18100,9 @@ $.ImageLoader.prototype = {
17907
18100
  * @param callback - Called once cleanup is finished.
17908
18101
  */
17909
18102
  function completeJob(loader, job, callback) {
18103
+ if (job.errorMsg !== '' && (job.data === null || job.data === undefined) && job.tries < 1 + loader.tileRetryMax) {
18104
+ loader.failedTiles.push(job);
18105
+ }
17910
18106
  var nextJob;
17911
18107
 
17912
18108
  loader.jobsInProgress--;
@@ -17917,6 +18113,16 @@ function completeJob(loader, job, callback) {
17917
18113
  loader.jobsInProgress++;
17918
18114
  }
17919
18115
 
18116
+ if (loader.tileRetryMax > 0 && loader.jobQueue.length === 0) {
18117
+ if ((!loader.jobLimit || loader.jobsInProgress < loader.jobLimit) && loader.failedTiles.length > 0) {
18118
+ nextJob = loader.failedTiles.shift();
18119
+ setTimeout(function () {
18120
+ nextJob.start();
18121
+ }, loader.tileRetryDelay);
18122
+ loader.jobsInProgress++;
18123
+ }
18124
+ }
18125
+
17920
18126
  callback(job.data, job.errorMsg, job.request);
17921
18127
  }
17922
18128
 
@@ -20438,6 +20644,8 @@ $.Viewport.prototype = {
20438
20644
 
20439
20645
  if(constraints){
20440
20646
  this.panTo(center, false);
20647
+
20648
+ newZoom = this._applyZoomConstraints(newZoom);
20441
20649
  this.zoomTo(newZoom, null, false);
20442
20650
 
20443
20651
  var constrainedBounds = this.getConstrainedBounds();
@@ -20803,7 +21011,10 @@ $.Viewport.prototype = {
20803
21011
 
20804
21012
  if( this.viewer ){
20805
21013
  /**
20806
- * Raised when the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).
21014
+ * Raised when a viewer resize operation is initiated (see {@link OpenSeadragon.Viewport#resize}).
21015
+ * This event happens before the viewport bounds have been updated.
21016
+ * See also {@link OpenSeadragon.Viewer#after-resize} which reflects
21017
+ * the new viewport bounds following the resize action.
20807
21018
  *
20808
21019
  * @event resize
20809
21020
  * @memberof OpenSeadragon.Viewer
@@ -20819,7 +21030,29 @@ $.Viewport.prototype = {
20819
21030
  });
20820
21031
  }
20821
21032
 
20822
- return this.fitBounds( newBounds, true );
21033
+ var output = this.fitBounds( newBounds, true );
21034
+
21035
+ if( this.viewer ){
21036
+ /**
21037
+ * Raised after the viewer is resized (see {@link OpenSeadragon.Viewport#resize}).
21038
+ * See also {@link OpenSeadragon.Viewer#resize} event which happens
21039
+ * before the new bounds have been calculated and applied.
21040
+ *
21041
+ * @event after-resize
21042
+ * @memberof OpenSeadragon.Viewer
21043
+ * @type {object}
21044
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
21045
+ * @property {OpenSeadragon.Point} newContainerSize
21046
+ * @property {Boolean} maintain
21047
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
21048
+ */
21049
+ this.viewer.raiseEvent( 'after-resize', {
21050
+ newContainerSize: newContainerSize,
21051
+ maintain: maintain
21052
+ });
21053
+ }
21054
+
21055
+ return output;
20823
21056
  },
20824
21057
 
20825
21058
  // private
@@ -21638,6 +21871,9 @@ $.TiledImage = function( options ) {
21638
21871
  var degrees = options.degrees || 0;
21639
21872
  delete options.degrees;
21640
21873
 
21874
+ var ajaxHeaders = options.ajaxHeaders;
21875
+ delete options.ajaxHeaders;
21876
+
21641
21877
  $.extend( true, this, {
21642
21878
 
21643
21879
  //internal state properties
@@ -21729,6 +21965,9 @@ $.TiledImage = function( options ) {
21729
21965
  tiledImage: _this
21730
21966
  }, args));
21731
21967
  };
21968
+
21969
+ this._ownAjaxHeaders = {};
21970
+ this.setAjaxHeaders(ajaxHeaders, false);
21732
21971
  };
21733
21972
 
21734
21973
  $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadragon.TiledImage.prototype */{
@@ -22494,6 +22733,90 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
22494
22733
  });
22495
22734
  },
22496
22735
 
22736
+ /**
22737
+ * Update headers to include when making AJAX requests.
22738
+ *
22739
+ * Unless `propagate` is set to false (which is likely only useful in rare circumstances),
22740
+ * the updated headers are propagated to all tiles and queued image loader jobs.
22741
+ *
22742
+ * Note that the rules for merging headers still apply, i.e. headers returned by
22743
+ * {@link OpenSeadragon.TileSource#getTileAjaxHeaders} take precedence over
22744
+ * the headers here in the tiled image (`TiledImage.ajaxHeaders`).
22745
+ *
22746
+ * @function
22747
+ * @param {Object} ajaxHeaders Updated AJAX headers, which will be merged over any headers specified in {@link OpenSeadragon.Options}.
22748
+ * @param {Boolean} [propagate=true] Whether to propagate updated headers to existing tiles and queued image loader jobs.
22749
+ */
22750
+ setAjaxHeaders: function(ajaxHeaders, propagate) {
22751
+ if (ajaxHeaders === null) {
22752
+ ajaxHeaders = {};
22753
+ }
22754
+ if (!$.isPlainObject(ajaxHeaders)) {
22755
+ console.error('[TiledImage.setAjaxHeaders] Ignoring invalid headers, must be a plain object');
22756
+ return;
22757
+ }
22758
+
22759
+ this._ownAjaxHeaders = ajaxHeaders;
22760
+ this._updateAjaxHeaders(propagate);
22761
+ },
22762
+
22763
+ /**
22764
+ * Update headers to include when making AJAX requests.
22765
+ *
22766
+ * This function has the same effect as calling {@link OpenSeadragon.TiledImage#setAjaxHeaders},
22767
+ * except that the headers for this tiled image do not change. This is especially useful
22768
+ * for propagating updated headers from {@link OpenSeadragon.TileSource#getTileAjaxHeaders}
22769
+ * to existing tiles.
22770
+ *
22771
+ * @private
22772
+ * @function
22773
+ * @param {Boolean} [propagate=true] Whether to propagate updated headers to existing tiles and queued image loader jobs.
22774
+ */
22775
+ _updateAjaxHeaders: function(propagate) {
22776
+ if (propagate === undefined) {
22777
+ propagate = true;
22778
+ }
22779
+
22780
+ // merge with viewer's headers
22781
+ if ($.isPlainObject(this.viewer.ajaxHeaders)) {
22782
+ this.ajaxHeaders = $.extend({}, this.viewer.ajaxHeaders, this._ownAjaxHeaders);
22783
+ } else {
22784
+ this.ajaxHeaders = this._ownAjaxHeaders;
22785
+ }
22786
+
22787
+ // propagate header updates to all tiles and queued image loader jobs
22788
+ if (propagate) {
22789
+ var numTiles, xMod, yMod, tile;
22790
+
22791
+ for (var level in this.tilesMatrix) {
22792
+ numTiles = this.source.getNumTiles(level);
22793
+
22794
+ for (var x in this.tilesMatrix[level]) {
22795
+ xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
22796
+
22797
+ for (var y in this.tilesMatrix[level][x]) {
22798
+ yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
22799
+ tile = this.tilesMatrix[level][x][y];
22800
+
22801
+ tile.loadWithAjax = this.loadTilesWithAjax;
22802
+ if (tile.loadWithAjax) {
22803
+ var tileAjaxHeaders = this.source.getTileAjaxHeaders( level, xMod, yMod );
22804
+ tile.ajaxHeaders = $.extend({}, this.ajaxHeaders, tileAjaxHeaders);
22805
+ } else {
22806
+ tile.ajaxHeaders = null;
22807
+ }
22808
+ }
22809
+ }
22810
+ }
22811
+
22812
+ for (var i = 0; i < this._imageLoader.jobQueue.length; i++) {
22813
+ var job = this._imageLoader.jobQueue[i];
22814
+ job.loadWithAjax = job.tile.loadWithAjax;
22815
+ job.ajaxHeaders = job.tile.loadWithAjax ? job.tile.ajaxHeaders : null;
22816
+ }
22817
+ }
22818
+ },
22819
+
22497
22820
  // private
22498
22821
  _setScale: function(scale, immediately) {
22499
22822
  var sameTarget = (this._scaleSpring.target.value === scale);
@@ -23112,6 +23435,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
23112
23435
  tile.loading = false;
23113
23436
  tile.exists = false;
23114
23437
  return;
23438
+ } else {
23439
+ tile.exists = true;
23115
23440
  }
23116
23441
 
23117
23442
  if ( time < this.lastResetTime ) {
@@ -23147,9 +23472,14 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
23147
23472
  */
23148
23473
  _setTileLoaded: function(tile, data, cutoff, tileRequest) {
23149
23474
  var increment = 0,
23475
+ eventFinished = false,
23150
23476
  _this = this;
23151
23477
 
23152
23478
  function getCompletionCallback() {
23479
+ if (eventFinished) {
23480
+ $.console.error("Event 'tile-loaded' argument getCompletionCallback must be called synchronously. " +
23481
+ "Its return value should be called asynchronously.");
23482
+ }
23153
23483
  increment++;
23154
23484
  return completionCallback;
23155
23485
  }
@@ -23191,6 +23521,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
23191
23521
  * marked as entirely loaded when the callback has been called once for each
23192
23522
  * call to getCompletionCallback.
23193
23523
  */
23524
+
23525
+ var fallbackCompletion = getCompletionCallback();
23194
23526
  this.viewer.raiseEvent("tile-loaded", {
23195
23527
  tile: tile,
23196
23528
  tiledImage: this,
@@ -23202,8 +23534,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
23202
23534
  data: data,
23203
23535
  getCompletionCallback: getCompletionCallback
23204
23536
  });
23537
+ eventFinished = true;
23205
23538
  // In case the completion callback is never called, we at least force it once.
23206
- getCompletionCallback()();
23539
+ fallbackCompletion();
23207
23540
  },
23208
23541
 
23209
23542
  /**
@@ -23445,6 +23778,9 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
23445
23778
  if (sketchScale) {
23446
23779
  clipPoint = clipPoint.times(sketchScale);
23447
23780
  }
23781
+ if (sketchTranslate) {
23782
+ clipPoint = clipPoint.plus(sketchTranslate);
23783
+ }
23448
23784
  return clipPoint;
23449
23785
  });
23450
23786
  });