openseadragon 0.5.0 → 0.6.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8d66f78f52a81526049f7be9b14ddc64669a5977
4
- data.tar.gz: 248b34157489eb40c6cdfbf9e2dac3b2fd1aca87
2
+ SHA256:
3
+ metadata.gz: 67814ae82d0be47642e41c48f3d7b5bad24091a61cd4f64cf7ff1051058c12ff
4
+ data.tar.gz: 8ad5f8638aeb244c6c16edfc0e3c8fa929301b6e982e241ab27b46d148abe91f
5
5
  SHA512:
6
- metadata.gz: 3aa9e60434688b0484bafd24a0a78c24a83331c85875f2e020ac5b0705de6419b7eab9a70d76479485f0ea8e0bc5ca46d9bb0b164a7ce0ce41b1a5696fbbc9a1
7
- data.tar.gz: fd097f52ff4581b8da589e06ddd02e009539da2f632e545b6e97f211a51c71b46fb5f4f3caf50ac305842d46716f71af08d4238eccf18c6eac522f9f92f2614d
6
+ metadata.gz: 6c06d4052b7ef3b3f02b97ed260afe69e94910c6e19161bddbb5756d13a929356059843c24fd8d6c782298aefdb94f87c5ad7f89f5d02ba1fab43c206022cefd
7
+ data.tar.gz: a5d2d03f9472d3c6333c5ebdd390a4157e5d6f6f0eea10e66e1c9c03d43adf628284ca3043206548178df39c7b28b487acb0a8d4227fe6572a2d5abeb9405ee3
@@ -0,0 +1,45 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ rails_version: [6.1.1, 6.0.3.2]
15
+ ruby: [2.7, 3.0]
16
+ env:
17
+ RAILS_VERSION: ${{ matrix.rails_version }}
18
+ steps:
19
+ - uses: actions/checkout@v2
20
+ - name: Set up Ruby ${{ matrix.ruby }}
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby }}
24
+ - name: Install dependencies with Rails ${{ matrix.rails_version }}
25
+ run: bundle install
26
+ - name: Run tests
27
+ run: bundle exec rake
28
+ test_rails52:
29
+ runs-on: ubuntu-latest
30
+ strategy:
31
+ matrix:
32
+ rails_version: [5.2.4.4]
33
+ ruby: [2.7]
34
+ env:
35
+ RAILS_VERSION: ${{ matrix.rails_version }}
36
+ steps:
37
+ - uses: actions/checkout@v2
38
+ - name: Set up Ruby ${{ matrix.ruby }}
39
+ uses: ruby/setup-ruby@v1
40
+ with:
41
+ ruby-version: ${{ matrix.ruby }}
42
+ - name: Install dependencies with Rails ${{ matrix.rails_version }}
43
+ run: bundle install
44
+ - name: Run tests
45
+ run: bundle exec rake
data/Gemfile CHANGED
@@ -21,19 +21,12 @@ else
21
21
  if ENV['RAILS_VERSION']
22
22
  if ENV['RAILS_VERSION'] == 'edge'
23
23
  gem 'rails', github: 'rails/rails'
24
- ENV['ENGINE_CART_RAILS_OPTIONS']= "--edge --skip-turbolinks"
24
+ ENV['ENGINE_CART_RAILS_OPTIONS'] = '--edge --skip-turbolinks'
25
+ elsif ENV['RAILS_VERSION'] < '6'
26
+ gem 'sprockets', '< 4'
25
27
  else
26
28
  gem 'rails', ENV['RAILS_VERSION']
27
29
  end
28
30
  end
29
-
30
- if ENV['RAILS_VERSION'].nil? || ENV['RAILS_VERSION'] =~ /^4.2/
31
- gem 'responders', "~> 2.0"
32
- gem 'sass-rails', ">= 5.0"
33
- elsif ENV['RAILS_VERSION'] =~ /^5.0/ || ENV['RAILS_VERSION'] == 'edge'
34
- # nop
35
- else
36
- gem 'sass-rails', "< 5.0"
37
- end
38
31
  end
39
32
  # END ENGINE_CART BLOCK
@@ -0,0 +1 @@
1
+ //= link_tree ../../../vendor/assets/images
@@ -3,10 +3,14 @@ require 'rails/generators'
3
3
  module Openseadragon
4
4
  class Install < Rails::Generators::Base
5
5
  source_root File.expand_path('../templates', __FILE__)
6
-
6
+
7
7
  def assets
8
8
  copy_file "openseadragon.css", "app/assets/stylesheets/openseadragon.css"
9
9
  copy_file "openseadragon.js", "app/assets/javascripts/openseadragon.js"
10
+
11
+ if File.exist? 'app/assets/config/manifest.js'
12
+ append_to_file 'app/assets/config/manifest.js', "\n//= link openseadragon-assets\n"
13
+ end
10
14
  end
11
15
 
12
16
  def inject_helper
@@ -15,4 +19,4 @@ module Openseadragon
15
19
  end
16
20
  end
17
21
  end
18
- end
22
+ end
@@ -1,4 +1,4 @@
1
- module Openseadragon
1
+ module Openseadragon
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Openseadragon
4
4
 
@@ -1,3 +1,3 @@
1
1
  module Openseadragon
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.5"
21
+ spec.add_development_dependency "bundler"
22
22
  spec.add_development_dependency "rake"
23
23
  spec.add_development_dependency "engine_cart"
24
24
  spec.add_development_dependency "rspec-rails", '~> 3.1'
@@ -1,6 +1,6 @@
1
- //! openseadragon 2.3.0
2
- //! Built on 2017-07-14
3
- //! Git commit: v2.3.0-2-b49fef3
1
+ //! openseadragon 2.4.2
2
+ //! Built on 2020-03-05
3
+ //! Git commit: v2.4.2-0-c450749
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 2.3.0
93
+ * @version openseadragon 2.4.2
94
94
  * @classdesc The root namespace for OpenSeadragon. All utility methods
95
95
  * and classes are defined on or below this namespace.
96
96
  *
@@ -165,7 +165,9 @@
165
165
  * @property {Boolean} [debugMode=false]
166
166
  * TODO: provide an in-screen panel providing event detail feedback.
167
167
  *
168
- * @property {String} [debugGridColor='#437AB2']
168
+ * @property {String} [debugGridColor=['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']]
169
+ * The colors of grids in debug mode. Each tiled image's grid uses a consecutive color.
170
+ * If there are more tiled images than provided colors, the color vector is recycled.
169
171
  *
170
172
  * @property {Number} [blendTime=0]
171
173
  * Specifies the duration of animation as higher or lower level tiles are
@@ -202,6 +204,11 @@
202
204
  * 'destination-over', 'destination-atop', 'destination-in',
203
205
  * 'destination-out', 'lighter', 'copy' or 'xor'
204
206
  *
207
+ * @property {Boolean} [imageSmoothingEnabled=true]
208
+ * Image smoothing for canvas rendering (only if canvas is used). Note: Ignored
209
+ * by some (especially older) browsers which do not support this canvas property.
210
+ * This property can be changed in {@link Viewer.Drawer.setImageSmoothingEnabled}.
211
+ *
205
212
  * @property {String|CanvasGradient|CanvasPattern|Function} [placeholderFillStyle=null]
206
213
  * Draws a colored rectangle behind the tile if it is not loaded yet.
207
214
  * You can pass a CSS color value like "#FF8800".
@@ -210,6 +217,9 @@
210
217
  * @property {Number} [degrees=0]
211
218
  * Initial rotation.
212
219
  *
220
+ * @property {Boolean} [flipped=false]
221
+ * Initial flip state.
222
+ *
213
223
  * @property {Number} [minZoomLevel=null]
214
224
  *
215
225
  * @property {Number} [maxZoomLevel=null]
@@ -243,7 +253,7 @@
243
253
  * The maximum ratio to allow a zoom-in to affect the highest level pixel
244
254
  * ratio. This can be set to Infinity to allow 'infinite' zooming into the
245
255
  * image though it is less effective visually if the HTML5 Canvas is not
246
- * availble on the viewing device.
256
+ * available on the viewing device.
247
257
  *
248
258
  * @property {Number} [smoothTileEdgesMinZoom=1.1]
249
259
  * A zoom percentage ( where 1 is 100% ) of the highest resolution level.
@@ -267,9 +277,15 @@
267
277
  * events between different devices, causing the faster devices to slow down enough to make the zoom control
268
278
  * more manageable.
269
279
  *
280
+ * @property {Number} [rotationIncrement=90]
281
+ * The number of degrees to rotate right or left when the rotate buttons or keyboard shortcuts are activated.
282
+ *
270
283
  * @property {Number} [pixelsPerWheelLine=40]
271
284
  * For pixel-resolution scrolling devices, the number of pixels equal to one scroll line.
272
285
  *
286
+ * @property {Number} [pixelsPerArrowPress=40]
287
+ * The number of pixels viewport moves when an arrow key is pressed.
288
+ *
273
289
  * @property {Number} [visibilityRatio=0.5]
274
290
  * The percentage ( as a number from 0 to 1 ) of the source image which
275
291
  * must be kept within the viewport. If the image is dragged beyond that
@@ -315,6 +331,8 @@
315
331
  * @property {Boolean} [gestureSettingsMouse.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true
316
332
  * then clickToZoom should be set to false to prevent multiple zooms.
317
333
  * @property {Boolean} [gestureSettingsMouse.pinchToZoom=false] - Zoom on pinch gesture
334
+ * @property {Boolean} [gestureSettingsMouse.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
335
+ * the zoom is centered at the canvas center.
318
336
  * @property {Boolean} [gestureSettingsMouse.flickEnabled=false] - Enable flick gesture
319
337
  * @property {Number} [gestureSettingsMouse.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
320
338
  * @property {Number} [gestureSettingsMouse.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
@@ -327,6 +345,8 @@
327
345
  * @property {Boolean} [gestureSettingsTouch.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
328
346
  * then clickToZoom should be set to false to prevent multiple zooms.
329
347
  * @property {Boolean} [gestureSettingsTouch.pinchToZoom=true] - Zoom on pinch gesture
348
+ * @property {Boolean} [gestureSettingsTouch.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
349
+ * the zoom is centered at the canvas center.
330
350
  * @property {Boolean} [gestureSettingsTouch.flickEnabled=true] - Enable flick gesture
331
351
  * @property {Number} [gestureSettingsTouch.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
332
352
  * @property {Number} [gestureSettingsTouch.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
@@ -339,6 +359,8 @@
339
359
  * @property {Boolean} [gestureSettingsPen.dblClickToZoom=false] - Zoom on double-click gesture. Note: If set to true
340
360
  * then clickToZoom should be set to false to prevent multiple zooms.
341
361
  * @property {Boolean} [gestureSettingsPen.pinchToZoom=false] - Zoom on pinch gesture
362
+ * @property {Boolean} [gestureSettingsPen.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
363
+ * the zoom is centered at the canvas center.
342
364
  * @property {Boolean} [gestureSettingsPen.flickEnabled=false] - Enable flick gesture
343
365
  * @property {Number} [gestureSettingsPen.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
344
366
  * @property {Number} [gestureSettingsPen.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
@@ -351,6 +373,8 @@
351
373
  * @property {Boolean} [gestureSettingsUnknown.dblClickToZoom=true] - Zoom on double-click gesture. Note: If set to true
352
374
  * then clickToZoom should be set to false to prevent multiple zooms.
353
375
  * @property {Boolean} [gestureSettingsUnknown.pinchToZoom=true] - Zoom on pinch gesture
376
+ * @property {Boolean} [gestureSettingsUnknown.zoomToRefPoint=true] - If zoomToRefPoint is true, the zoom is centered at the pointer position. Otherwise,
377
+ * the zoom is centered at the canvas center.
354
378
  * @property {Boolean} [gestureSettingsUnknown.flickEnabled=true] - Enable flick gesture
355
379
  * @property {Number} [gestureSettingsUnknown.flickMinSpeed=120] - If flickEnabled is true, the minimum speed to initiate a flick gesture (pixels-per-second)
356
380
  * @property {Number} [gestureSettingsUnknown.flickMomentum=0.25] - If flickEnabled is true, the momentum factor for the flick gesture
@@ -409,9 +433,21 @@
409
433
  * @property {Boolean} [navigatorRotate=true]
410
434
  * If true, the navigator will be rotated together with the viewer.
411
435
  *
436
+ * @property {String} [navigatorBackground='#000']
437
+ * Specifies the background color of the navigator minimap
438
+ *
439
+ * @property {Number} [navigatorOpacity=0.8]
440
+ * Specifies the opacity of the navigator minimap.
441
+ *
442
+ * @property {String} [navigatorBorderColor='#555']
443
+ * Specifies the border color of the navigator minimap
444
+ *
445
+ * @property {String} [navigatorDisplayRegionColor='#900']
446
+ * Specifies the border color of the display region rectangle of the navigator minimap
447
+ *
412
448
  * @property {Number} [controlsFadeDelay=2000]
413
449
  * The number of milliseconds to wait once the user has stopped interacting
414
- * with the interface before begining to fade the controls. Assumes
450
+ * with the interface before beginning to fade the controls. Assumes
415
451
  * showNavigationControl and autoHideControls are both true.
416
452
  *
417
453
  * @property {Number} [controlsFadeLength=1500]
@@ -429,7 +465,7 @@
429
465
  * @property {Number} [minPixelRatio=0.5]
430
466
  * The higher the minPixelRatio, the lower the quality of the image that
431
467
  * is considered sufficient to stop rendering a given zoom level. For
432
- * example, if you are targeting mobile devices with less bandwith you may
468
+ * example, if you are targeting mobile devices with less bandwidth you may
433
469
  * try setting this to 1.5 or higher.
434
470
  *
435
471
  * @property {Boolean} [mouseNavEnabled=true]
@@ -471,6 +507,10 @@
471
507
  * Note: {@link OpenSeadragon.Options.showNavigationControl} is overriding
472
508
  * this setting when set to false.
473
509
  *
510
+ * @property {Boolean} [showFlipControl=false]
511
+ * If true then the flip controls will be displayed as part of the
512
+ * standard controls.
513
+ *
474
514
  * @property {Boolean} [showSequenceControl=true]
475
515
  * If sequenceMode is true, then provide buttons for navigating forward and
476
516
  * backward through the images.
@@ -681,6 +721,12 @@
681
721
  * @property {String} rotateright.HOVER
682
722
  * @property {String} rotateright.DOWN
683
723
  *
724
+ * @property {Object} flip - Images for the flip button.
725
+ * @property {String} flip.REST
726
+ * @property {String} flip.GROUP
727
+ * @property {String} flip.HOVER
728
+ * @property {String} flip.DOWN
729
+ *
684
730
  * @property {Object} previous - Images for the previous button.
685
731
  * @property {String} previous.REST
686
732
  * @property {String} previous.GROUP
@@ -695,14 +741,7 @@
695
741
  *
696
742
  */
697
743
 
698
-
699
- /**
700
- * This function serves as a single point of instantiation for an {@link OpenSeadragon.Viewer}, including all
701
- * combinations of out-of-the-box configurable features.
702
- *
703
- * @param {OpenSeadragon.Options} options - Viewer options.
704
- * @returns {OpenSeadragon.Viewer}
705
- */
744
+ /* eslint-disable no-redeclare */
706
745
  function OpenSeadragon( options ){
707
746
  return new OpenSeadragon.Viewer( options );
708
747
  }
@@ -721,10 +760,10 @@ function OpenSeadragon( options ){
721
760
  * @since 1.0.0
722
761
  */
723
762
  $.version = {
724
- versionStr: '2.3.0',
763
+ versionStr: '2.4.2',
725
764
  major: parseInt('2', 10),
726
- minor: parseInt('3', 10),
727
- revision: parseInt('0', 10)
765
+ minor: parseInt('4', 10),
766
+ revision: parseInt('2', 10)
728
767
  };
729
768
 
730
769
 
@@ -1052,6 +1091,7 @@ function OpenSeadragon( options ){
1052
1091
  clickToZoom: true,
1053
1092
  dblClickToZoom: false,
1054
1093
  pinchToZoom: false,
1094
+ zoomToRefPoint: true,
1055
1095
  flickEnabled: false,
1056
1096
  flickMinSpeed: 120,
1057
1097
  flickMomentum: 0.25,
@@ -1062,6 +1102,7 @@ function OpenSeadragon( options ){
1062
1102
  clickToZoom: false,
1063
1103
  dblClickToZoom: true,
1064
1104
  pinchToZoom: true,
1105
+ zoomToRefPoint: true,
1065
1106
  flickEnabled: true,
1066
1107
  flickMinSpeed: 120,
1067
1108
  flickMomentum: 0.25,
@@ -1072,6 +1113,7 @@ function OpenSeadragon( options ){
1072
1113
  clickToZoom: true,
1073
1114
  dblClickToZoom: false,
1074
1115
  pinchToZoom: false,
1116
+ zoomToRefPoint: true,
1075
1117
  flickEnabled: false,
1076
1118
  flickMinSpeed: 120,
1077
1119
  flickMomentum: 0.25,
@@ -1082,6 +1124,7 @@ function OpenSeadragon( options ){
1082
1124
  clickToZoom: false,
1083
1125
  dblClickToZoom: true,
1084
1126
  pinchToZoom: true,
1127
+ zoomToRefPoint: true,
1085
1128
  flickEnabled: true,
1086
1129
  flickMinSpeed: 120,
1087
1130
  flickMomentum: 0.25,
@@ -1099,9 +1142,11 @@ function OpenSeadragon( options ){
1099
1142
  smoothTileEdgesMinZoom: 1.1, //-> higher than maxZoomPixelRatio disables it
1100
1143
  iOSDevice: isIOSDevice(),
1101
1144
  pixelsPerWheelLine: 40,
1145
+ pixelsPerArrowPress: 40,
1102
1146
  autoResize: true,
1103
1147
  preserveImageSizeOnResize: false, // requires autoResize=true
1104
1148
  minScrollDeltaTime: 50,
1149
+ rotationIncrement: 90,
1105
1150
 
1106
1151
  //DEFAULT CONTROL SETTINGS
1107
1152
  showSequenceControl: true, //SEQUENCE
@@ -1115,6 +1160,7 @@ function OpenSeadragon( options ){
1115
1160
  showHomeControl: true, //HOME
1116
1161
  showFullPageControl: true, //FULL
1117
1162
  showRotationControl: false, //ROTATION
1163
+ showFlipControl: false, //FLIP
1118
1164
  controlsFadeDelay: 2000, //ZOOM/HOME/FULL/SEQUENCE
1119
1165
  controlsFadeLength: 1500, //ZOOM/HOME/FULL/SEQUENCE
1120
1166
  mouseNavEnabled: true, //GENERAL MOUSE INTERACTIVITY
@@ -1132,14 +1178,22 @@ function OpenSeadragon( options ){
1132
1178
  navigatorAutoResize: true,
1133
1179
  navigatorAutoFade: true,
1134
1180
  navigatorRotate: true,
1181
+ navigatorBackground: '#000',
1182
+ navigatorOpacity: 0.8,
1183
+ navigatorBorderColor: '#555',
1184
+ navigatorDisplayRegionColor: '#900',
1135
1185
 
1136
1186
  // INITIAL ROTATION
1137
1187
  degrees: 0,
1138
1188
 
1189
+ // INITIAL FLIP STATE
1190
+ flipped: false,
1191
+
1139
1192
  // APPEARANCE
1140
1193
  opacity: 1,
1141
1194
  preload: false,
1142
1195
  compositeOperation: null,
1196
+ imageSmoothingEnabled: true,
1143
1197
  placeholderFillStyle: null,
1144
1198
 
1145
1199
  //REFERENCE STRIP SETTINGS
@@ -1204,6 +1258,12 @@ function OpenSeadragon( options ){
1204
1258
  HOVER: 'rotateright_hover.png',
1205
1259
  DOWN: 'rotateright_pressed.png'
1206
1260
  },
1261
+ flip: { // Flip icon designed by Yaroslav Samoylov from the Noun Project and modified by Nelson Campos ncampos@criteriamarathon.com, https://thenounproject.com/term/flip/136289/
1262
+ REST: 'flip_rest.png',
1263
+ GROUP: 'flip_grouphover.png',
1264
+ HOVER: 'flip_hover.png',
1265
+ DOWN: 'flip_pressed.png'
1266
+ },
1207
1267
  previous: {
1208
1268
  REST: 'previous_rest.png',
1209
1269
  GROUP: 'previous_grouphover.png',
@@ -1220,7 +1280,7 @@ function OpenSeadragon( options ){
1220
1280
 
1221
1281
  //DEVELOPER SETTINGS
1222
1282
  debugMode: false,
1223
- debugGridColor: '#437AB2'
1283
+ debugGridColor: ['#437AB2', '#1B9E77', '#D95F02', '#7570B3', '#E7298A', '#66A61E', '#E6AB02', '#A6761D', '#666666']
1224
1284
  },
1225
1285
 
1226
1286
 
@@ -1289,7 +1349,7 @@ function OpenSeadragon( options ){
1289
1349
  /**
1290
1350
  * Determines the position of the upper-left corner of the element.
1291
1351
  * @function
1292
- * @param {Element|String} element - the elemenet we want the position for.
1352
+ * @param {Element|String} element - the element we want the position for.
1293
1353
  * @returns {OpenSeadragon.Point} - the position of the upper left corner of the element.
1294
1354
  */
1295
1355
  getElementPosition: function( element ) {
@@ -1494,7 +1554,7 @@ function OpenSeadragon( options ){
1494
1554
  */
1495
1555
  getMousePosition: function( event ) {
1496
1556
 
1497
- if ( typeof( event.pageX ) == "number" ) {
1557
+ if ( typeof ( event.pageX ) == "number" ) {
1498
1558
  $.getMousePosition = function( event ){
1499
1559
  var result = new $.Point();
1500
1560
 
@@ -1504,7 +1564,7 @@ function OpenSeadragon( options ){
1504
1564
 
1505
1565
  return result;
1506
1566
  };
1507
- } else if ( typeof( event.clientX ) == "number" ) {
1567
+ } else if ( typeof ( event.clientX ) == "number" ) {
1508
1568
  $.getMousePosition = function( event ){
1509
1569
  var result = new $.Point();
1510
1570
 
@@ -1539,7 +1599,7 @@ function OpenSeadragon( options ){
1539
1599
  var docElement = document.documentElement || {},
1540
1600
  body = document.body || {};
1541
1601
 
1542
- if ( typeof( window.pageXOffset ) == "number" ) {
1602
+ if ( typeof ( window.pageXOffset ) == "number" ) {
1543
1603
  $.getPageScroll = function(){
1544
1604
  return new $.Point(
1545
1605
  window.pageXOffset,
@@ -1628,7 +1688,7 @@ function OpenSeadragon( options ){
1628
1688
  var docElement = document.documentElement || {},
1629
1689
  body = document.body || {};
1630
1690
 
1631
- if ( typeof( window.innerWidth ) == 'number' ) {
1691
+ if ( typeof ( window.innerWidth ) == 'number' ) {
1632
1692
  $.getWindowSize = function(){
1633
1693
  return new $.Point(
1634
1694
  window.innerWidth,
@@ -2032,7 +2092,7 @@ function OpenSeadragon( options ){
2032
2092
  /**
2033
2093
  * Similar to OpenSeadragon.delegate, but it does not immediately call
2034
2094
  * the method on the object, returning a function which can be called
2035
- * repeatedly to delegate the method. It also allows additonal arguments
2095
+ * repeatedly to delegate the method. It also allows additional arguments
2036
2096
  * to be passed during construction which will be added during each
2037
2097
  * invocation, and each invocation can add additional arguments as well.
2038
2098
  *
@@ -2066,7 +2126,7 @@ function OpenSeadragon( options ){
2066
2126
 
2067
2127
 
2068
2128
  /**
2069
- * Retreives the value of a url parameter from the window.location string.
2129
+ * Retrieves the value of a url parameter from the window.location string.
2070
2130
  * @function
2071
2131
  * @param {String} key
2072
2132
  * @returns {String} The value of the url parameter or null if no param matches.
@@ -2202,7 +2262,7 @@ function OpenSeadragon( options ){
2202
2262
 
2203
2263
  if (headers) {
2204
2264
  for (var headerName in headers) {
2205
- if (headers.hasOwnProperty(headerName) && headers[headerName]) {
2265
+ if (Object.prototype.hasOwnProperty.call(headers, headerName) && headers[headerName]) {
2206
2266
  request.setRequestHeader(headerName, headers[headerName]);
2207
2267
  }
2208
2268
  }
@@ -2228,7 +2288,7 @@ function OpenSeadragon( options ){
2228
2288
  error messages are localized.
2229
2289
  */
2230
2290
  var oldIE = $.Browser.vendor == $.BROWSERS.IE && $.Browser.version < 10;
2231
- if ( oldIE && typeof( e.number ) != "undefined" && e.number == -2147024891 ) {
2291
+ if ( oldIE && typeof ( e.number ) != "undefined" && e.number == -2147024891 ) {
2232
2292
  msg += "\nSee http://msdn.microsoft.com/en-us/library/ms537505(v=vs.85).aspx#xdomain";
2233
2293
  }
2234
2294
 
@@ -2237,7 +2297,7 @@ function OpenSeadragon( options ){
2237
2297
  request.onreadystatechange = function(){};
2238
2298
 
2239
2299
  if (window.XDomainRequest) { // IE9 or IE8 might as well try to use XDomainRequest
2240
- var xdr = new XDomainRequest();
2300
+ var xdr = new window.XDomainRequest();
2241
2301
  if (xdr) {
2242
2302
  xdr.onload = function (e) {
2243
2303
  if ( $.isFunction( onSuccess ) ) {
@@ -2437,6 +2497,31 @@ function OpenSeadragon( options ){
2437
2497
  });
2438
2498
 
2439
2499
 
2500
+ //TODO: $.console is often used inside a try/catch block which generally
2501
+ // prevents allowings errors to occur with detection until a debugger
2502
+ // is attached. Although I've been guilty of the same anti-pattern
2503
+ // I eventually was convinced that errors should naturally propagate in
2504
+ // all but the most special cases.
2505
+ /**
2506
+ * A convenient alias for console when available, and a simple null
2507
+ * function when console is unavailable.
2508
+ * @static
2509
+ * @private
2510
+ */
2511
+ var nullfunction = function( msg ){
2512
+ //document.location.hash = msg;
2513
+ };
2514
+
2515
+ $.console = window.console || {
2516
+ log: nullfunction,
2517
+ debug: nullfunction,
2518
+ info: nullfunction,
2519
+ warn: nullfunction,
2520
+ error: nullfunction,
2521
+ assert: nullfunction
2522
+ };
2523
+
2524
+
2440
2525
  /**
2441
2526
  * The current browser vendor, version, and related information regarding detected features.
2442
2527
  * @member {Object} Browser
@@ -2532,8 +2617,13 @@ function OpenSeadragon( options ){
2532
2617
  sep = part.indexOf( '=' );
2533
2618
 
2534
2619
  if ( sep > 0 ) {
2535
- URLPARAMS[ part.substring( 0, sep ) ] =
2536
- decodeURIComponent( part.substring( sep + 1 ) );
2620
+ var key = part.substring( 0, sep ),
2621
+ value = part.substring( sep + 1 );
2622
+ try {
2623
+ URLPARAMS[ key ] = decodeURIComponent( value );
2624
+ } catch (e) {
2625
+ $.console.error( "Ignoring malformed URL parameter: %s=%s", key, value );
2626
+ }
2537
2627
  }
2538
2628
  }
2539
2629
 
@@ -2557,31 +2647,6 @@ function OpenSeadragon( options ){
2557
2647
  })();
2558
2648
 
2559
2649
 
2560
- //TODO: $.console is often used inside a try/catch block which generally
2561
- // prevents allowings errors to occur with detection until a debugger
2562
- // is attached. Although I've been guilty of the same anti-pattern
2563
- // I eventually was convinced that errors should naturally propogate in
2564
- // all but the most special cases.
2565
- /**
2566
- * A convenient alias for console when available, and a simple null
2567
- * function when console is unavailable.
2568
- * @static
2569
- * @private
2570
- */
2571
- var nullfunction = function( msg ){
2572
- //document.location.hash = msg;
2573
- };
2574
-
2575
- $.console = window.console || {
2576
- log: nullfunction,
2577
- debug: nullfunction,
2578
- info: nullfunction,
2579
- warn: nullfunction,
2580
- error: nullfunction,
2581
- assert: nullfunction
2582
- };
2583
-
2584
-
2585
2650
  // Adding support for HTML5's requestAnimationFrame as suggested by acdha.
2586
2651
  // Implementation taken from matt synder's post here:
2587
2652
  // http://mattsnider.com/cross-browser-and-legacy-supported-requestframeanimation/
@@ -4279,8 +4344,9 @@ $.EventSource.prototype = {
4279
4344
  },
4280
4345
 
4281
4346
  /**
4282
- * @function Increment this pointer's contact count.
4347
+ * Increment this pointer's contact count.
4283
4348
  * It will evaluate whether this pointer type is allowed to have multiple contacts.
4349
+ * @function
4284
4350
  */
4285
4351
  addContact: function() {
4286
4352
  ++this.contacts;
@@ -4291,8 +4357,9 @@ $.EventSource.prototype = {
4291
4357
  },
4292
4358
 
4293
4359
  /**
4294
- * @function Decrement this pointer's contact count.
4360
+ * Decrement this pointer's contact count.
4295
4361
  * It will make sure the count does not go below 0.
4362
+ * @function
4296
4363
  */
4297
4364
  removeContact: function() {
4298
4365
  --this.contacts;
@@ -5538,7 +5605,7 @@ $.EventSource.prototype = {
5538
5605
 
5539
5606
  // If isPrimary is not known for the pointer then set it according to our rules:
5540
5607
  // true if the first pointer in the gesture, otherwise false
5541
- if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
5608
+ if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) {
5542
5609
  if ( pointsList.getLength() === 0 ) {
5543
5610
  gPoint.isPrimary = true;
5544
5611
  } else {
@@ -5574,7 +5641,7 @@ $.EventSource.prototype = {
5574
5641
  listLength = pointsList.removeById( gPoint.id );
5575
5642
 
5576
5643
  // If isPrimary is not known for the pointer and we just removed the primary pointer from the list then we need to set another pointer as primary
5577
- if ( !gPoint.hasOwnProperty( 'isPrimary' ) ) {
5644
+ if ( !Object.prototype.hasOwnProperty.call( gPoint, 'isPrimary' ) ) {
5578
5645
  primaryPoint = pointsList.getPrimary();
5579
5646
  if ( !primaryPoint ) {
5580
5647
  primaryPoint = pointsList.getByIndex( 0 );
@@ -5700,7 +5767,7 @@ $.EventSource.prototype = {
5700
5767
  {
5701
5768
  eventSource: tracker,
5702
5769
  pointerType: curGPoint.type,
5703
- position: getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
5770
+ position: curGPoint.currentPos && getPointRelativeToAbsolute( curGPoint.currentPos, tracker.element ),
5704
5771
  buttons: pointsList.buttons,
5705
5772
  pointers: tracker.getActivePointerCount(),
5706
5773
  insideElementPressed: updateGPoint ? updateGPoint.insideElementPressed : false,
@@ -5997,10 +6064,16 @@ $.EventSource.prototype = {
5997
6064
  return false;
5998
6065
  }
5999
6066
 
6067
+ // OS-specific gestures (e.g. swipe up with four fingers in iPadOS 13)
6068
+ if (typeof gPoints[ 0 ].currentPos === "undefined") {
6069
+ abortContacts(tracker, event, pointsList);
6070
+
6071
+ return false;
6072
+ }
6073
+
6000
6074
  for ( i = 0; i < gPointCount; i++ ) {
6001
6075
  curGPoint = gPoints[ i ];
6002
6076
  updateGPoint = pointsList.getById( curGPoint.id );
6003
-
6004
6077
  if ( updateGPoint ) {
6005
6078
  // Update the pointer, stop tracking it if not still in this element
6006
6079
  if ( updateGPoint.captured ) {
@@ -6205,7 +6278,7 @@ $.EventSource.prototype = {
6205
6278
 
6206
6279
  if ( updateGPoint ) {
6207
6280
  // Already tracking the pointer...update it
6208
- if ( curGPoint.hasOwnProperty( 'isPrimary' ) ) {
6281
+ if ( Object.prototype.hasOwnProperty.call( curGPoint, 'isPrimary' ) ) {
6209
6282
  updateGPoint.isPrimary = curGPoint.isPrimary;
6210
6283
  }
6211
6284
  updateGPoint.lastPos = updateGPoint.currentPos;
@@ -6382,10 +6455,12 @@ $.EventSource.prototype = {
6382
6455
  }
6383
6456
  }
6384
6457
 
6385
- // True if inside an iframe, otherwise false.
6386
- // @member {Boolean} isInIframe
6387
- // @private
6388
- // @inner
6458
+ /**
6459
+ * True if inside an iframe, otherwise false.
6460
+ * @member {Boolean} isInIframe
6461
+ * @private
6462
+ * @inner
6463
+ */
6389
6464
  var isInIframe = (function() {
6390
6465
  try {
6391
6466
  return window.self !== window.top;
@@ -6394,10 +6469,12 @@ $.EventSource.prototype = {
6394
6469
  }
6395
6470
  })();
6396
6471
 
6397
- // @function
6398
- // @private
6399
- // @inner
6400
- // @returns {Boolean} True if the target has access rights to events, otherwise false.
6472
+ /**
6473
+ * @function
6474
+ * @private
6475
+ * @inner
6476
+ * @returns {Boolean} True if the target has access rights to events, otherwise false.
6477
+ */
6401
6478
  function canAccessEvents (target) {
6402
6479
  try {
6403
6480
  return target.addEventListener && target.removeEventListener;
@@ -6577,7 +6654,7 @@ $.Control.prototype = {
6577
6654
  /**
6578
6655
  * Determines if the control is currently visible.
6579
6656
  * @function
6580
- * @return {Boolean} true if currenly visible, false otherwise.
6657
+ * @return {Boolean} true if currently visible, false otherwise.
6581
6658
  */
6582
6659
  isVisible: function() {
6583
6660
  return this.wrapper.style.display != "none";
@@ -7050,7 +7127,7 @@ $.Viewer = function( options ) {
7050
7127
  i;
7051
7128
 
7052
7129
 
7053
- //backward compatibility for positional args while prefering more
7130
+ //backward compatibility for positional args while preferring more
7054
7131
  //idiomatic javascript options object as the only argument
7055
7132
  if( !$.isPlainObject( options ) ){
7056
7133
  options = {
@@ -7126,6 +7203,8 @@ $.Viewer = function( options ) {
7126
7203
 
7127
7204
  //These are originally not part options but declared as members
7128
7205
  //in initialize. It's still considered idiomatic to put them here
7206
+ //source is here for backwards compatibility. It is not an official
7207
+ //part of the API and should not be relied upon.
7129
7208
  source: null,
7130
7209
  /**
7131
7210
  * Handles rendering of tiles in the viewer. Created for each TileSource opened.
@@ -7133,6 +7212,11 @@ $.Viewer = function( options ) {
7133
7212
  * @memberof OpenSeadragon.Viewer#
7134
7213
  */
7135
7214
  drawer: null,
7215
+ /**
7216
+ * Keeps track of all of the tiled images in the scene.
7217
+ * @member {OpenSeadragon.World} world
7218
+ * @memberof OpenSeadragon.Viewer#
7219
+ */
7136
7220
  world: null,
7137
7221
  /**
7138
7222
  * Handles coordinate-related functionality - zoom, pan, rotation, etc. Created for each TileSource opened.
@@ -7163,10 +7247,10 @@ $.Viewer = function( options ) {
7163
7247
 
7164
7248
  }, $.DEFAULT_SETTINGS, options );
7165
7249
 
7166
- if ( typeof( this.hash) === "undefined" ) {
7250
+ if ( typeof ( this.hash) === "undefined" ) {
7167
7251
  throw new Error("A hash must be defined, either by specifying options.id or options.hash.");
7168
7252
  }
7169
- if ( typeof( THIS[ this.hash ] ) !== "undefined" ) {
7253
+ if ( typeof ( THIS[ this.hash ] ) !== "undefined" ) {
7170
7254
  // We don't want to throw an error here, as the user might have discarded
7171
7255
  // the previous viewer with the same hash and now want to recreate it.
7172
7256
  $.console.warn("Hash " + this.hash + " has already been used.");
@@ -7348,6 +7432,7 @@ $.Viewer = function( options ) {
7348
7432
  maxZoomLevel: this.maxZoomLevel,
7349
7433
  viewer: this,
7350
7434
  degrees: this.degrees,
7435
+ flipped: this.flipped,
7351
7436
  navigatorRotate: this.navigatorRotate,
7352
7437
  homeFillsViewer: this.homeFillsViewer,
7353
7438
  margins: this.viewportMargins
@@ -7409,6 +7494,10 @@ $.Viewer = function( options ) {
7409
7494
  prefixUrl: this.prefixUrl,
7410
7495
  viewer: this,
7411
7496
  navigatorRotate: this.navigatorRotate,
7497
+ background: this.navigatorBackground,
7498
+ opacity: this.navigatorOpacity,
7499
+ borderColor: this.navigatorBorderColor,
7500
+ displayRegionColor: this.navigatorDisplayRegionColor,
7412
7501
  crossOriginPolicy: this.crossOriginPolicy
7413
7502
  });
7414
7503
  }
@@ -7435,6 +7524,12 @@ $.Viewer = function( options ) {
7435
7524
  $.requestAnimationFrame( function(){
7436
7525
  beginControlsAutoHide( _this );
7437
7526
  } );
7527
+
7528
+ // Initial canvas options
7529
+ if ( this.imageSmoothingEnabled !== undefined && !this.imageSmoothingEnabled){
7530
+ this.drawer.setImageSmoothingEnabled(this.imageSmoothingEnabled);
7531
+ }
7532
+
7438
7533
  };
7439
7534
 
7440
7535
  $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype, /** @lends OpenSeadragon.Viewer.prototype */{
@@ -7462,6 +7557,9 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7462
7557
 
7463
7558
  /**
7464
7559
  * Open tiled images into the viewer, closing any others.
7560
+ * To get the TiledImage instance created by open, add an event listener for
7561
+ * {@link OpenSeadragon.Viewer.html#.event:open}, which when fired can be used to get access
7562
+ * to the instance, i.e., viewer.world.getItemAt(0).
7465
7563
  * @function
7466
7564
  * @param {Array|String|Object|Function} tileSources - This can be a TiledImage
7467
7565
  * specifier, a TileSource specifier, or an array of either. A TiledImage specifier
@@ -7795,7 +7893,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7795
7893
  var enabled = this.controls.length,
7796
7894
  i;
7797
7895
  for( i = 0; i < this.controls.length; i++ ){
7798
- enabled = enabled && this.controls[ i ].isVisibile();
7896
+ enabled = enabled && this.controls[ i ].isVisible();
7799
7897
  }
7800
7898
  return enabled;
7801
7899
  },
@@ -7872,7 +7970,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7872
7970
  nodes,
7873
7971
  i;
7874
7972
 
7875
- //dont bother modifying the DOM if we are already in full page mode.
7973
+ //don't bother modifying the DOM if we are already in full page mode.
7876
7974
  if ( fullPage == this.isFullPage() ) {
7877
7975
  return this;
7878
7976
  }
@@ -7927,7 +8025,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
7927
8025
  bodyStyle.height = "100%";
7928
8026
  docStyle.height = "100%";
7929
8027
 
7930
- //when entering full screen on the ipad it wasnt sufficient to leave
8028
+ //when entering full screen on the ipad it wasn't sufficient to leave
7931
8029
  //the body intact as only only the top half of the screen would
7932
8030
  //respond to touch events on the canvas, while the bottom half treated
7933
8031
  //them as touch events on the document body. Thus we remove and store
@@ -8134,7 +8232,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8134
8232
  }
8135
8233
  }
8136
8234
  if ( _this.navigator && _this.viewport ) {
8137
- _this.navigator.update( _this.viewport );
8235
+ //09/08/2018 - Fabroh : Fix issue #1504 : Ensure to get the navigator updated on fullscreen out with custom location with a timeout
8236
+ setTimeout(function(){
8237
+ _this.navigator.update( _this.viewport );
8238
+ });
8138
8239
  }
8139
8240
  /**
8140
8241
  * Raised when the viewer has changed to/from full-screen mode (see {@link OpenSeadragon.Viewer#setFullScreen}).
@@ -8244,7 +8345,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8244
8345
  * requests.
8245
8346
  * @param {Function} [options.success] A function that gets called when the image is
8246
8347
  * successfully added. It's passed the event object which contains a single property:
8247
- * "item", the resulting TiledImage.
8348
+ * "item", which is the resulting instance of TiledImage.
8248
8349
  * @param {Function} [options.error] A function that gets called if the image is
8249
8350
  * unable to be added. It's passed the error event object, which contains "message"
8250
8351
  * and "source" properties.
@@ -8652,6 +8753,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8652
8753
  onFullScreenHandler = $.delegate( this, onFullScreen ),
8653
8754
  onRotateLeftHandler = $.delegate( this, onRotateLeft ),
8654
8755
  onRotateRightHandler = $.delegate( this, onRotateRight ),
8756
+ onFlipHandler = $.delegate( this, onFlip),
8655
8757
  onFocusHandler = $.delegate( this, onFocus ),
8656
8758
  onBlurHandler = $.delegate( this, onBlur ),
8657
8759
  navImages = this.navImages,
@@ -8663,7 +8765,8 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8663
8765
 
8664
8766
  if( this.zoomInButton || this.zoomOutButton ||
8665
8767
  this.homeButton || this.fullPageButton ||
8666
- this.rotateLeftButton || this.rotateRightButton ) {
8768
+ this.rotateLeftButton || this.rotateRightButton ||
8769
+ this.flipButton ) {
8667
8770
  //if we are binding to custom buttons then layout and
8668
8771
  //grouping is the responsibility of the page author
8669
8772
  useGroup = false;
@@ -8767,7 +8870,22 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8767
8870
  onFocus: onFocusHandler,
8768
8871
  onBlur: onBlurHandler
8769
8872
  }));
8873
+ }
8770
8874
 
8875
+ if ( this.showFlipControl ) {
8876
+ buttons.push( this.flipButton = new $.Button({
8877
+ element: this.flipButton ? $.getElement( this.flipButton ) : null,
8878
+ clickTimeThreshold: this.clickTimeThreshold,
8879
+ clickDistThreshold: this.clickDistThreshold,
8880
+ tooltip: $.getString( "Tooltips.Flip" ),
8881
+ srcRest: resolveUrl( this.prefixUrl, navImages.flip.REST ),
8882
+ srcGroup: resolveUrl( this.prefixUrl, navImages.flip.GROUP ),
8883
+ srcHover: resolveUrl( this.prefixUrl, navImages.flip.HOVER ),
8884
+ srcDown: resolveUrl( this.prefixUrl, navImages.flip.DOWN ),
8885
+ onRelease: onFlipHandler,
8886
+ onFocus: onFocusHandler,
8887
+ onBlur: onBlurHandler
8888
+ }));
8771
8889
  }
8772
8890
 
8773
8891
  if ( useGroup ) {
@@ -8813,6 +8931,16 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8813
8931
  */
8814
8932
  goToPage: function( page ){
8815
8933
  if( this.tileSources && page >= 0 && page < this.tileSources.length ){
8934
+ this._sequenceIndex = page;
8935
+
8936
+ this._updateSequenceButtons( page );
8937
+
8938
+ this.open( this.tileSources[ page ] );
8939
+
8940
+ if( this.referenceStrip ){
8941
+ this.referenceStrip.setFocus( page );
8942
+ }
8943
+
8816
8944
  /**
8817
8945
  * Raised when the page is changed on a viewer configured with multiple image sources (see {@link OpenSeadragon.Viewer#goToPage}).
8818
8946
  *
@@ -8824,16 +8952,6 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8824
8952
  * @property {?Object} userData - Arbitrary subscriber-defined object.
8825
8953
  */
8826
8954
  this.raiseEvent( 'page', { page: page } );
8827
-
8828
- this._sequenceIndex = page;
8829
-
8830
- this._updateSequenceButtons( page );
8831
-
8832
- this.open( this.tileSources[ page ] );
8833
-
8834
- if( this.referenceStrip ){
8835
- this.referenceStrip.setFocus( page );
8836
- }
8837
8955
  }
8838
8956
 
8839
8957
  return this;
@@ -8846,15 +8964,15 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8846
8964
  * is closed which include when changing page.
8847
8965
  * @method
8848
8966
  * @param {Element|String|Object} element - A reference to an element or an id for
8849
- * the element which will be overlayed. Or an Object specifying the configuration for the overlay.
8967
+ * the element which will be overlaid. Or an Object specifying the configuration for the overlay.
8850
8968
  * If using an object, see {@link OpenSeadragon.Overlay} for a list of
8851
8969
  * all available options.
8852
8970
  * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
8853
- * rectangle which will be overlayed. This is a viewport relative location.
8854
- * @param {OpenSeadragon.Placement} placement - The position of the
8971
+ * rectangle which will be overlaid. This is a viewport relative location.
8972
+ * @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the
8855
8973
  * viewport which the location coordinates will be treated as relative
8856
8974
  * to.
8857
- * @param {function} onDraw - If supplied the callback is called when the overlay
8975
+ * @param {function} [onDraw] - If supplied the callback is called when the overlay
8858
8976
  * needs to be drawn. It it the responsibility of the callback to do any drawing/positioning.
8859
8977
  * It is passed position, size and element.
8860
8978
  * @return {OpenSeadragon.Viewer} Chainable.
@@ -8909,10 +9027,10 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
8909
9027
  * element id moving it to the new location, relative to the new placement.
8910
9028
  * @method
8911
9029
  * @param {Element|String} element - A reference to an element or an id for
8912
- * the element which is overlayed.
9030
+ * the element which is overlaid.
8913
9031
  * @param {OpenSeadragon.Point|OpenSeadragon.Rect} location - The point or
8914
- * rectangle which will be overlayed. This is a viewport relative location.
8915
- * @param {OpenSeadragon.Placement} placement - The position of the
9032
+ * rectangle which will be overlaid. This is a viewport relative location.
9033
+ * @param {OpenSeadragon.Placement} [placement=OpenSeadragon.Placement.TOP_LEFT] - The position of the
8916
9034
  * viewport which the location coordinates will be treated as relative
8917
9035
  * to.
8918
9036
  * @return {OpenSeadragon.Viewer} Chainable.
@@ -9167,6 +9285,7 @@ $.extend( $.Viewer.prototype, $.EventSource.prototype, $.ControlDock.prototype,
9167
9285
  width: this.referenceStripWidth,
9168
9286
  tileSources: this.tileSources,
9169
9287
  prefixUrl: this.prefixUrl,
9288
+ useCanvas: this.useCanvas,
9170
9289
  viewer: this
9171
9290
  });
9172
9291
 
@@ -9455,31 +9574,62 @@ function onBlur(){
9455
9574
  }
9456
9575
 
9457
9576
  function onCanvasKeyDown( event ) {
9458
- if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
9577
+ var canvasKeyDownEventArgs = {
9578
+ originalEvent: event.originalEvent,
9579
+ preventDefaultAction: event.preventDefaultAction,
9580
+ preventVerticalPan: event.preventVerticalPan,
9581
+ preventHorizontalPan: event.preventHorizontalPan
9582
+ };
9583
+
9584
+ /**
9585
+ * Raised when a keyboard key is pressed and the focus is on the {@link OpenSeadragon.Viewer#canvas} element.
9586
+ *
9587
+ * @event canvas-key
9588
+ * @memberof OpenSeadragon.Viewer
9589
+ * @type {object}
9590
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
9591
+ * @property {Object} originalEvent - The original DOM event.
9592
+ * @property {Boolean} preventDefaultAction - Set to true to prevent default keyboard behaviour. Default: false.
9593
+ * @property {Boolean} preventVerticalPan - Set to true to prevent keyboard vertical panning. Default: false.
9594
+ * @property {Boolean} preventHorizontalPan - Set to true to prevent keyboard horizontal panning. Default: false.
9595
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
9596
+ */
9597
+
9598
+ this.raiseEvent('canvas-key', canvasKeyDownEventArgs);
9599
+
9600
+ if ( !canvasKeyDownEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
9459
9601
  switch( event.keyCode ){
9460
9602
  case 38://up arrow
9461
- if ( event.shift ) {
9603
+ if (!canvasKeyDownEventArgs.preventVerticalPan) {
9604
+ if ( event.shift ) {
9462
9605
  this.viewport.zoomBy(1.1);
9463
- } else {
9464
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
9606
+ } else {
9607
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -this.pixelsPerArrowPress)));
9608
+ }
9609
+ this.viewport.applyConstraints();
9465
9610
  }
9466
- this.viewport.applyConstraints();
9467
9611
  return false;
9468
9612
  case 40://down arrow
9469
- if ( event.shift ) {
9613
+ if (!canvasKeyDownEventArgs.preventVerticalPan) {
9614
+ if ( event.shift ) {
9470
9615
  this.viewport.zoomBy(0.9);
9471
- } else {
9472
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
9616
+ } else {
9617
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, this.pixelsPerArrowPress)));
9618
+ }
9619
+ this.viewport.applyConstraints();
9473
9620
  }
9474
- this.viewport.applyConstraints();
9475
9621
  return false;
9476
9622
  case 37://left arrow
9477
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
9478
- this.viewport.applyConstraints();
9623
+ if (!canvasKeyDownEventArgs.preventHorizontalPan) {
9624
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-this.pixelsPerArrowPress, 0)));
9625
+ this.viewport.applyConstraints();
9626
+ }
9479
9627
  return false;
9480
9628
  case 39://right arrow
9481
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
9482
- this.viewport.applyConstraints();
9629
+ if (!canvasKeyDownEventArgs.preventHorizontalPan) {
9630
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(this.pixelsPerArrowPress, 0)));
9631
+ this.viewport.applyConstraints();
9632
+ }
9483
9633
  return false;
9484
9634
  default:
9485
9635
  //console.log( 'navigator keycode %s', event.keyCode );
@@ -9489,9 +9639,18 @@ function onCanvasKeyDown( event ) {
9489
9639
  return true;
9490
9640
  }
9491
9641
  }
9492
-
9493
9642
  function onCanvasKeyPress( event ) {
9494
- if ( !event.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
9643
+ var canvasKeyPressEventArgs = {
9644
+ originalEvent: event.originalEvent,
9645
+ preventDefaultAction: event.preventDefaultAction,
9646
+ preventVerticalPan: event.preventVerticalPan,
9647
+ preventHorizontalPan: event.preventHorizontalPan
9648
+ };
9649
+
9650
+ // This event is documented in onCanvasKeyDown
9651
+ this.raiseEvent('canvas-key', canvasKeyPressEventArgs);
9652
+
9653
+ if ( !canvasKeyPressEventArgs.preventDefaultAction && !event.ctrl && !event.alt && !event.meta ) {
9495
9654
  switch( event.keyCode ){
9496
9655
  case 43://=|+
9497
9656
  case 61://=|+
@@ -9508,32 +9667,59 @@ function onCanvasKeyPress( event ) {
9508
9667
  return false;
9509
9668
  case 119://w
9510
9669
  case 87://W
9511
- if ( event.shift ) {
9512
- this.viewport.zoomBy(1.1);
9513
- } else {
9514
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
9515
- }
9516
- this.viewport.applyConstraints();
9517
- return false;
9670
+ if (!canvasKeyPressEventArgs.preventVerticalPan) {
9671
+ if ( event.shift ) {
9672
+ this.viewport.zoomBy(1.1);
9673
+ } else {
9674
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, -40)));
9675
+ }
9676
+ this.viewport.applyConstraints();
9677
+ }
9678
+ return false;
9518
9679
  case 115://s
9519
9680
  case 83://S
9520
- if ( event.shift ) {
9681
+ if (!canvasKeyPressEventArgs.preventVerticalPan) {
9682
+ if ( event.shift ) {
9521
9683
  this.viewport.zoomBy(0.9);
9522
- } else {
9684
+ } else {
9523
9685
  this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(0, 40)));
9686
+ }
9687
+ this.viewport.applyConstraints();
9524
9688
  }
9525
- this.viewport.applyConstraints();
9526
9689
  return false;
9527
9690
  case 97://a
9528
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
9529
- this.viewport.applyConstraints();
9691
+ if (!canvasKeyPressEventArgs.preventHorizontalPan) {
9692
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(-40, 0)));
9693
+ this.viewport.applyConstraints();
9694
+ }
9530
9695
  return false;
9531
9696
  case 100://d
9532
- this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
9533
- this.viewport.applyConstraints();
9697
+ if (!canvasKeyPressEventArgs.preventHorizontalPan) {
9698
+ this.viewport.panBy(this.viewport.deltaPointsFromPixels(new $.Point(40, 0)));
9699
+ this.viewport.applyConstraints();
9700
+ }
9534
9701
  return false;
9702
+ case 114: //r - clockwise rotation
9703
+ if(this.viewport.flipped){
9704
+ this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
9705
+ } else{
9706
+ this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
9707
+ }
9708
+ this.viewport.applyConstraints();
9709
+ return false;
9710
+ case 82: //R - counterclockwise rotation
9711
+ if(this.viewport.flipped){
9712
+ this.viewport.setRotation($.positiveModulo(this.viewport.degrees + this.rotationIncrement, 360));
9713
+ } else{
9714
+ this.viewport.setRotation($.positiveModulo(this.viewport.degrees - this.rotationIncrement, 360));
9715
+ }
9716
+ this.viewport.applyConstraints();
9717
+ return false;
9718
+ case 102: //f
9719
+ this.viewport.toggleFlip();
9720
+ return false;
9535
9721
  default:
9536
- //console.log( 'navigator keycode %s', event.keyCode );
9722
+ // console.log( 'navigator keycode %s', event.keyCode );
9537
9723
  return true;
9538
9724
  }
9539
9725
  } else {
@@ -9550,6 +9736,9 @@ function onCanvasClick( event ) {
9550
9736
  if ( !haveKeyboardFocus ) {
9551
9737
  this.canvas.focus();
9552
9738
  }
9739
+ if(this.viewport.flipped){
9740
+ event.position.x = this.viewport.getContainerSize().x - event.position.x;
9741
+ }
9553
9742
 
9554
9743
  var canvasClickEventArgs = {
9555
9744
  tracker: event.eventSource,
@@ -9582,7 +9771,7 @@ function onCanvasClick( event ) {
9582
9771
  if ( gestureSettings.clickToZoom ) {
9583
9772
  this.viewport.zoomBy(
9584
9773
  event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
9585
- this.viewport.pointFromPixel( event.position, true )
9774
+ gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
9586
9775
  );
9587
9776
  this.viewport.applyConstraints();
9588
9777
  }
@@ -9592,16 +9781,14 @@ function onCanvasClick( event ) {
9592
9781
  function onCanvasDblClick( event ) {
9593
9782
  var gestureSettings;
9594
9783
 
9595
- if ( !event.preventDefaultAction && this.viewport ) {
9596
- gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9597
- if ( gestureSettings.dblClickToZoom ) {
9598
- this.viewport.zoomBy(
9599
- event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
9600
- this.viewport.pointFromPixel( event.position, true )
9601
- );
9602
- this.viewport.applyConstraints();
9603
- }
9604
- }
9784
+ var canvasDblClickEventArgs = {
9785
+ tracker: event.eventSource,
9786
+ position: event.position,
9787
+ shift: event.shift,
9788
+ originalEvent: event.originalEvent,
9789
+ preventDefaultAction: event.preventDefaultAction
9790
+ };
9791
+
9605
9792
  /**
9606
9793
  * Raised when a double mouse press/release or touch/remove occurs on the {@link OpenSeadragon.Viewer#canvas} element.
9607
9794
  *
@@ -9613,14 +9800,21 @@ function onCanvasDblClick( event ) {
9613
9800
  * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
9614
9801
  * @property {Boolean} shift - True if the shift key was pressed during this event.
9615
9802
  * @property {Object} originalEvent - The original DOM event.
9803
+ * @property {Boolean} preventDefaultAction - Set to true to prevent default double tap to zoom behaviour. Default: false.
9616
9804
  * @property {?Object} userData - Arbitrary subscriber-defined object.
9617
9805
  */
9618
- this.raiseEvent( 'canvas-double-click', {
9619
- tracker: event.eventSource,
9620
- position: event.position,
9621
- shift: event.shift,
9622
- originalEvent: event.originalEvent
9623
- });
9806
+ this.raiseEvent( 'canvas-double-click', canvasDblClickEventArgs);
9807
+
9808
+ if ( !canvasDblClickEventArgs.preventDefaultAction && this.viewport ) {
9809
+ gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9810
+ if ( gestureSettings.dblClickToZoom ) {
9811
+ this.viewport.zoomBy(
9812
+ event.shift ? 1.0 / this.zoomPerClick : this.zoomPerClick,
9813
+ gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
9814
+ );
9815
+ this.viewport.applyConstraints();
9816
+ }
9817
+ }
9624
9818
  }
9625
9819
 
9626
9820
  function onCanvasDrag( event ) {
@@ -9636,6 +9830,7 @@ function onCanvasDrag( event ) {
9636
9830
  originalEvent: event.originalEvent,
9637
9831
  preventDefaultAction: event.preventDefaultAction
9638
9832
  };
9833
+
9639
9834
  /**
9640
9835
  * Raised when a mouse or touch drag operation occurs on the {@link OpenSeadragon.Viewer#canvas} element.
9641
9836
  *
@@ -9655,7 +9850,7 @@ function onCanvasDrag( event ) {
9655
9850
  */
9656
9851
  this.raiseEvent( 'canvas-drag', canvasDragEventArgs);
9657
9852
 
9658
- if ( !event.preventDefaultAction && this.viewport ) {
9853
+ if ( !canvasDragEventArgs.preventDefaultAction && this.viewport ) {
9659
9854
  gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9660
9855
  if( !this.panHorizontal ){
9661
9856
  event.delta.x = 0;
@@ -9663,6 +9858,9 @@ function onCanvasDrag( event ) {
9663
9858
  if( !this.panVertical ){
9664
9859
  event.delta.y = 0;
9665
9860
  }
9861
+ if(this.viewport.flipped){
9862
+ event.delta.x = -event.delta.x;
9863
+ }
9666
9864
 
9667
9865
  if( this.constrainDuringPan ){
9668
9866
  var delta = this.viewport.deltaPointsFromPixels( event.delta.negate() );
@@ -9929,7 +10127,9 @@ function onCanvasPinch( event ) {
9929
10127
  panByPt.y = 0;
9930
10128
  }
9931
10129
  this.viewport.zoomBy( event.distance / event.lastDistance, centerPt, true );
9932
- this.viewport.panBy( panByPt, true );
10130
+ if ( gestureSettings.zoomToRefPoint ) {
10131
+ this.viewport.panBy(panByPt, true);
10132
+ }
9933
10133
  this.viewport.applyConstraints();
9934
10134
  }
9935
10135
  if ( gestureSettings.pinchRotate ) {
@@ -9986,13 +10186,17 @@ function onCanvasScroll( event ) {
9986
10186
  if (deltaScrollTime > this.minScrollDeltaTime) {
9987
10187
  this._lastScrollTime = thisScrollTime;
9988
10188
 
10189
+ if(this.viewport.flipped){
10190
+ event.position.x = this.viewport.getContainerSize().x - event.position.x;
10191
+ }
10192
+
9989
10193
  if ( !event.preventDefaultAction && this.viewport ) {
9990
10194
  gestureSettings = this.gestureSettingsByDeviceType( event.pointerType );
9991
10195
  if ( gestureSettings.scrollToZoom ) {
9992
10196
  factor = Math.pow( this.zoomPerScroll, event.scroll );
9993
10197
  this.viewport.zoomBy(
9994
10198
  factor,
9995
- this.viewport.pointFromPixel( event.position, true )
10199
+ gestureSettings.zoomToRefPoint ? this.viewport.pointFromPixel( event.position, true ) : null
9996
10200
  );
9997
10201
  this.viewport.applyConstraints();
9998
10202
  }
@@ -10181,7 +10385,7 @@ function updateOnce( viewer ) {
10181
10385
  drawWorld( viewer );
10182
10386
  viewer._drawOverlays();
10183
10387
  if( viewer.navigator ){
10184
- viewer.navigator.update( viewer.viewport );
10388
+ viewer.navigator.update( viewer.viewport );
10185
10389
  }
10186
10390
 
10187
10391
  THIS[ viewer.hash ].forceRedraw = false;
@@ -10345,38 +10549,37 @@ function onFullScreen() {
10345
10549
  }
10346
10550
  }
10347
10551
 
10348
- /**
10349
- * Note: The current rotation feature is limited to 90 degree turns.
10350
- */
10351
10552
  function onRotateLeft() {
10352
10553
  if ( this.viewport ) {
10353
10554
  var currRotation = this.viewport.getRotation();
10354
- if (currRotation === 0) {
10355
- currRotation = 270;
10356
- }
10357
- else {
10358
- currRotation -= 90;
10555
+
10556
+ if ( this.viewport.flipped ){
10557
+ currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
10558
+ } else {
10559
+ currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
10359
10560
  }
10360
10561
  this.viewport.setRotation(currRotation);
10361
10562
  }
10362
10563
  }
10363
10564
 
10364
- /**
10365
- * Note: The current rotation feature is limited to 90 degree turns.
10366
- */
10367
10565
  function onRotateRight() {
10368
10566
  if ( this.viewport ) {
10369
10567
  var currRotation = this.viewport.getRotation();
10370
- if (currRotation === 270) {
10371
- currRotation = 0;
10372
- }
10373
- else {
10374
- currRotation += 90;
10568
+
10569
+ if ( this.viewport.flipped ){
10570
+ currRotation = $.positiveModulo(currRotation - this.rotationIncrement, 360);
10571
+ } else {
10572
+ currRotation = $.positiveModulo(currRotation + this.rotationIncrement, 360);
10375
10573
  }
10376
10574
  this.viewport.setRotation(currRotation);
10377
10575
  }
10378
10576
  }
10379
-
10577
+ /**
10578
+ * Note: When pressed flip control button
10579
+ */
10580
+ function onFlip() {
10581
+ this.viewport.toggleFlip();
10582
+ }
10380
10583
 
10381
10584
  function onPrevious(){
10382
10585
  var previous = this._sequenceIndex - 1;
@@ -10510,7 +10713,11 @@ $.Navigator = function( options ){
10510
10713
  animationTime: 0,
10511
10714
  autoResize: options.autoResize,
10512
10715
  // prevent resizing the navigator from adding unwanted space around the image
10513
- minZoomImageRatio: 1.0
10716
+ minZoomImageRatio: 1.0,
10717
+ background: options.background,
10718
+ opacity: options.opacity,
10719
+ borderColor: options.borderColor,
10720
+ displayRegionColor: options.displayRegionColor
10514
10721
  });
10515
10722
 
10516
10723
  options.minPixelRatio = this.minPixelRatio = viewer.minPixelRatio;
@@ -10527,10 +10734,10 @@ $.Navigator = function( options ){
10527
10734
  if ( options.controlOptions.anchor != $.ControlAnchor.NONE ) {
10528
10735
  (function( style, borderWidth ){
10529
10736
  style.margin = '0px';
10530
- style.border = borderWidth + 'px solid #555';
10737
+ style.border = borderWidth + 'px solid ' + options.borderColor;
10531
10738
  style.padding = '0px';
10532
- style.background = '#000';
10533
- style.opacity = 0.8;
10739
+ style.background = options.background;
10740
+ style.opacity = options.opacity;
10534
10741
  style.overflow = 'hidden';
10535
10742
  }( this.element.style, this.borderWidth));
10536
10743
  }
@@ -10545,10 +10752,10 @@ $.Navigator = function( options ){
10545
10752
  style.left = '0px';
10546
10753
  style.fontSize = '0px';
10547
10754
  style.overflow = 'hidden';
10548
- style.border = borderWidth + 'px solid #900';
10755
+ style.border = borderWidth + 'px solid ' + options.displayRegionColor;
10549
10756
  style.margin = '0px';
10550
10757
  style.padding = '0px';
10551
- //TODO: IE doesnt like this property being set
10758
+ //TODO: IE doesn't like this property being set
10552
10759
  //try{ style.outline = '2px auto #909'; }catch(e){/*ignore*/}
10553
10760
 
10554
10761
  style.background = 'transparent';
@@ -10578,16 +10785,14 @@ $.Navigator = function( options ){
10578
10785
  this._resizeWithViewer = options.controlOptions.anchor != $.ControlAnchor.ABSOLUTE &&
10579
10786
  options.controlOptions.anchor != $.ControlAnchor.NONE;
10580
10787
 
10581
- if ( this._resizeWithViewer ) {
10582
- if ( options.width && options.height ) {
10583
- this.element.style.height = typeof (options.height) == "number" ? (options.height + 'px') : options.height;
10584
- this.element.style.width = typeof (options.width) == "number" ? (options.width + 'px') : options.width;
10585
- } else {
10586
- viewerSize = $.getElementSize( viewer.element );
10587
- this.element.style.height = Math.round( viewerSize.y * options.sizeRatio ) + 'px';
10588
- this.element.style.width = Math.round( viewerSize.x * options.sizeRatio ) + 'px';
10589
- this.oldViewerSize = viewerSize;
10590
- }
10788
+ if (options.width && options.height) {
10789
+ this.setWidth(options.width);
10790
+ this.setHeight(options.height);
10791
+ } else if ( this._resizeWithViewer ) {
10792
+ viewerSize = $.getElementSize( viewer.element );
10793
+ this.element.style.height = Math.round( viewerSize.y * options.sizeRatio ) + 'px';
10794
+ this.element.style.width = Math.round( viewerSize.x * options.sizeRatio ) + 'px';
10795
+ this.oldViewerSize = viewerSize;
10591
10796
  navigatorSize = $.getElementSize( this.element );
10592
10797
  this.elementArea = navigatorSize.x * navigatorSize.y;
10593
10798
  }
@@ -10608,12 +10813,14 @@ $.Navigator = function( options ){
10608
10813
  var degrees = options.viewer.viewport ?
10609
10814
  options.viewer.viewport.getRotation() :
10610
10815
  options.viewer.degrees || 0;
10816
+
10611
10817
  rotate(degrees);
10612
10818
  options.viewer.addHandler("rotate", function (args) {
10613
10819
  rotate(args.degrees);
10614
10820
  });
10615
10821
  }
10616
10822
 
10823
+
10617
10824
  // Remove the base class' (Viewer's) innerTracker and replace it with our own
10618
10825
  this.innerTracker.destroy();
10619
10826
  this.innerTracker = new $.MouseTracker({
@@ -10672,6 +10879,43 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
10672
10879
  }
10673
10880
  },
10674
10881
 
10882
+ /**
10883
+ * Explicitly sets the width of the navigator, in web coordinates. Disables automatic resizing.
10884
+ * @param {Number|String} width - the new width, either a number of pixels or a CSS string, such as "100%"
10885
+ */
10886
+ setWidth: function(width) {
10887
+ this.width = width;
10888
+ this.element.style.width = typeof (width) == "number" ? (width + 'px') : width;
10889
+ this._resizeWithViewer = false;
10890
+ },
10891
+
10892
+ /**
10893
+ * Explicitly sets the height of the navigator, in web coordinates. Disables automatic resizing.
10894
+ * @param {Number|String} height - the new height, either a number of pixels or a CSS string, such as "100%"
10895
+ */
10896
+ setHeight: function(height) {
10897
+ this.height = height;
10898
+ this.element.style.height = typeof (height) == "number" ? (height + 'px') : height;
10899
+ this._resizeWithViewer = false;
10900
+ },
10901
+
10902
+ /**
10903
+ * Flip navigator element
10904
+ * @param {Boolean} state - Flip state to set.
10905
+ */
10906
+ setFlip: function(state) {
10907
+ this.viewport.setFlip(state);
10908
+
10909
+ this.setDisplayTransform(this.viewer.viewport.getFlip() ? "scale(-1,1)" : "scale(1,1)");
10910
+ return this;
10911
+ },
10912
+
10913
+ setDisplayTransform: function(rule) {
10914
+ setElementTransform(this.displayRegion, rule);
10915
+ setElementTransform(this.canvas, rule);
10916
+ setElementTransform(this.element, rule);
10917
+ },
10918
+
10675
10919
  /**
10676
10920
  * Used to update the navigator minimap's viewport rectangle when a change in the viewer's viewport occurs.
10677
10921
  * @function
@@ -10799,16 +11043,55 @@ $.extend( $.Navigator.prototype, $.EventSource.prototype, $.Viewer.prototype, /*
10799
11043
  }
10800
11044
  });
10801
11045
 
11046
+
10802
11047
  /**
10803
11048
  * @private
10804
11049
  * @inner
10805
11050
  * @function
10806
11051
  */
10807
11052
  function onCanvasClick( event ) {
10808
- if ( event.quick && this.viewer.viewport ) {
10809
- this.viewer.viewport.panTo(this.viewport.pointFromPixel(event.position));
10810
- this.viewer.viewport.applyConstraints();
10811
- }
11053
+ var canvasClickEventArgs = {
11054
+ tracker: event.eventSource,
11055
+ position: event.position,
11056
+ quick: event.quick,
11057
+ shift: event.shift,
11058
+ originalEvent: event.originalEvent,
11059
+ preventDefaultAction: event.preventDefaultAction
11060
+ };
11061
+ /**
11062
+ * Raised when a click event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
11063
+ *
11064
+ * @event navigator-click
11065
+ * @memberof OpenSeadragon.Viewer
11066
+ * @type {object}
11067
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
11068
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
11069
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
11070
+ * @property {Boolean} quick - True only if the clickDistThreshold and clickTimeThreshold are both passed. Useful for differentiating between clicks and drags.
11071
+ * @property {Boolean} shift - True if the shift key was pressed during this event.
11072
+ * @property {Object} originalEvent - The original DOM event.
11073
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
11074
+ * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
11075
+ */
11076
+
11077
+ this.viewer.raiseEvent('navigator-click', canvasClickEventArgs);
11078
+
11079
+ if ( !canvasClickEventArgs.preventDefaultAction && event.quick && this.viewer.viewport && (this.panVertical || this.panHorizontal)) {
11080
+ if(this.viewer.viewport.flipped) {
11081
+ event.position.x = this.viewport.getContainerSize().x - event.position.x;
11082
+ }
11083
+ var target = this.viewport.pointFromPixel(event.position);
11084
+ if (!this.panVertical) {
11085
+ // perform only horizonal pan
11086
+ target.y = this.viewer.viewport.getCenter(true).y;
11087
+ } else if (!this.panHorizontal) {
11088
+ // perform only vertical pan
11089
+ target.x = this.viewer.viewport.getCenter(true).x;
11090
+ }
11091
+ this.viewer.viewport.panTo(target);
11092
+ this.viewer.viewport.applyConstraints();
11093
+ }
11094
+
10812
11095
  }
10813
11096
 
10814
11097
  /**
@@ -10817,13 +11100,47 @@ function onCanvasClick( event ) {
10817
11100
  * @function
10818
11101
  */
10819
11102
  function onCanvasDrag( event ) {
10820
- if ( this.viewer.viewport ) {
10821
- if( !this.panHorizontal ){
11103
+ var canvasDragEventArgs = {
11104
+ tracker: event.eventSource,
11105
+ position: event.position,
11106
+ delta: event.delta,
11107
+ speed: event.speed,
11108
+ direction: event.direction,
11109
+ shift: event.shift,
11110
+ originalEvent: event.originalEvent,
11111
+ preventDefaultAction: event.preventDefaultAction
11112
+ };
11113
+ /**
11114
+ * Raised when a drag event occurs on the {@link OpenSeadragon.Viewer#navigator} element.
11115
+ *
11116
+ * @event navigator-drag
11117
+ * @memberof OpenSeadragon.Viewer
11118
+ * @type {object}
11119
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
11120
+ * @property {OpenSeadragon.MouseTracker} tracker - A reference to the MouseTracker which originated this event.
11121
+ * @property {OpenSeadragon.Point} position - The position of the event relative to the tracked element.
11122
+ * @property {OpenSeadragon.Point} delta - The x,y components of the difference between start drag and end drag.
11123
+ * @property {Number} speed - Current computed speed, in pixels per second.
11124
+ * @property {Number} direction - Current computed direction, expressed as an angle counterclockwise relative to the positive X axis (-pi to pi, in radians). Only valid if speed > 0.
11125
+ * @property {Boolean} shift - True if the shift key was pressed during this event.
11126
+ * @property {Object} originalEvent - The original DOM event.
11127
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
11128
+ * @property {Boolean} preventDefaultAction - Set to true to prevent default click to zoom behaviour. Default: false.
11129
+ */
11130
+ this.viewer.raiseEvent('navigator-drag', canvasDragEventArgs);
11131
+
11132
+ if ( !canvasDragEventArgs.preventDefaultAction && this.viewer.viewport ) {
11133
+ if( !this.panHorizontal ){
10822
11134
  event.delta.x = 0;
10823
11135
  }
10824
11136
  if( !this.panVertical ){
10825
11137
  event.delta.y = 0;
10826
11138
  }
11139
+
11140
+ if(this.viewer.viewport.flipped){
11141
+ event.delta.x = -event.delta.x;
11142
+ }
11143
+
10827
11144
  this.viewer.viewport.panBy(
10828
11145
  this.viewport.deltaPointsFromPixels(
10829
11146
  event.delta
@@ -10876,7 +11193,7 @@ function onCanvasScroll( event ) {
10876
11193
  originalEvent: event.originalEvent
10877
11194
  });
10878
11195
 
10879
- //dont scroll the page up and down if the user is scrolling
11196
+ //don't scroll the page up and down if the user is scrolling
10880
11197
  //in the navigator
10881
11198
  return false;
10882
11199
  }
@@ -10887,12 +11204,16 @@ function onCanvasScroll( event ) {
10887
11204
  * @param {Object} element
10888
11205
  * @param {Number} degrees
10889
11206
  */
10890
- function _setTransformRotate (element, degrees) {
10891
- element.style.webkitTransform = "rotate(" + degrees + "deg)";
10892
- element.style.mozTransform = "rotate(" + degrees + "deg)";
10893
- element.style.msTransform = "rotate(" + degrees + "deg)";
10894
- element.style.oTransform = "rotate(" + degrees + "deg)";
10895
- element.style.transform = "rotate(" + degrees + "deg)";
11207
+ function _setTransformRotate( element, degrees ) {
11208
+ setElementTransform(element, "rotate(" + degrees + "deg)");
11209
+ }
11210
+
11211
+ function setElementTransform( element, rule ) {
11212
+ element.style.webkitTransform = rule;
11213
+ element.style.mozTransform = rule;
11214
+ element.style.msTransform = rule;
11215
+ element.style.oTransform = rule;
11216
+ element.style.transform = rule;
10896
11217
  }
10897
11218
 
10898
11219
  }( OpenSeadragon ));
@@ -10956,7 +11277,8 @@ var I18N = {
10956
11277
  NextPage: "Next page",
10957
11278
  PreviousPage: "Previous page",
10958
11279
  RotateLeft: "Rotate left",
10959
- RotateRight: "Rotate right"
11280
+ RotateRight: "Rotate right",
11281
+ Flip: "Flip Horizontally"
10960
11282
  }
10961
11283
  };
10962
11284
 
@@ -10980,7 +11302,7 @@ $.extend( $, /** @lends OpenSeadragon */{
10980
11302
  }
10981
11303
  string = container[ props[ i ] ];
10982
11304
 
10983
- if ( typeof( string ) != "string" ) {
11305
+ if ( typeof ( string ) != "string" ) {
10984
11306
  $.console.log( "Untranslated source string:", prop );
10985
11307
  string = ""; // FIXME: this breaks gettext()-style convention, which would return source
10986
11308
  }
@@ -11104,10 +11426,10 @@ $.Point.prototype = {
11104
11426
  },
11105
11427
 
11106
11428
  /**
11107
- * Substract another Point to this point and return a new Point.
11429
+ * Subtract another Point to this point and return a new Point.
11108
11430
  * @function
11109
- * @param {OpenSeadragon.Point} point The point to substract vector components.
11110
- * @returns {OpenSeadragon.Point} A new point representing the substraction of the
11431
+ * @param {OpenSeadragon.Point} point The point to subtract vector components.
11432
+ * @returns {OpenSeadragon.Point} A new point representing the subtraction of the
11111
11433
  * vector components
11112
11434
  */
11113
11435
  minus: function( point ) {
@@ -11379,7 +11701,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
11379
11701
  //by asynchronously fetching their configuration data.
11380
11702
  $.EventSource.call( this );
11381
11703
 
11382
- //we allow options to override anything we dont treat as
11704
+ //we allow options to override anything we don't treat as
11383
11705
  //required via idiomatic options or which is functionally
11384
11706
  //set depending on the state of the readiness of this tile
11385
11707
  //source
@@ -11438,7 +11760,7 @@ $.TileSource = function( width, height, tileSize, tileOverlap, minLevel, maxLeve
11438
11760
  }
11439
11761
 
11440
11762
  if (this.url) {
11441
- //in case the getImageInfo method is overriden and/or implies an
11763
+ //in case the getImageInfo method is overridden and/or implies an
11442
11764
  //async mechanism set some safe defaults first
11443
11765
  this.aspectRatio = 1;
11444
11766
  this.dimensions = new $.Point( 10, 10 );
@@ -11505,7 +11827,7 @@ $.TileSource.prototype = {
11505
11827
 
11506
11828
  getTileSize: function( level ) {
11507
11829
  $.console.error(
11508
- "[TileSource.getTileSize] is deprecated." +
11830
+ "[TileSource.getTileSize] is deprecated. " +
11509
11831
  "Use TileSource.getTileWidth() and TileSource.getTileHeight() instead"
11510
11832
  );
11511
11833
  return this._tileWidth;
@@ -11626,7 +11948,8 @@ $.TileSource.prototype = {
11626
11948
  if (point.x >= 1) {
11627
11949
  x = this.getNumTiles(level).x - 1;
11628
11950
  }
11629
- if (point.y >= 1 / this.aspectRatio) {
11951
+ var EPSILON = 1e-15;
11952
+ if (point.y >= 1 / this.aspectRatio - EPSILON) {
11630
11953
  y = this.getNumTiles(level).y - 1;
11631
11954
  }
11632
11955
 
@@ -11638,8 +11961,12 @@ $.TileSource.prototype = {
11638
11961
  * @param {Number} level
11639
11962
  * @param {Number} x
11640
11963
  * @param {Number} y
11964
+ * @param {Boolean} [isSource=false] Whether to return the source bounds of the tile.
11965
+ * @returns {OpenSeadragon.Rect} Either where this tile fits (in normalized coordinates) or the
11966
+ * portion of the tile to use as the source of the drawing operation (in pixels), depending on
11967
+ * the isSource parameter.
11641
11968
  */
11642
- getTileBounds: function( level, x, y ) {
11969
+ getTileBounds: function( level, x, y, isSource ) {
11643
11970
  var dimensionsScaled = this.dimensions.times( this.getLevelScale( level ) ),
11644
11971
  tileWidth = this.getTileWidth(level),
11645
11972
  tileHeight = this.getTileHeight(level),
@@ -11652,6 +11979,10 @@ $.TileSource.prototype = {
11652
11979
  sx = Math.min( sx, dimensionsScaled.x - px );
11653
11980
  sy = Math.min( sy, dimensionsScaled.y - py );
11654
11981
 
11982
+ if (isSource) {
11983
+ return new $.Rect(0, 0, sx, sy);
11984
+ }
11985
+
11655
11986
  return new $.Rect( px * scale, py * scale, sx * scale, sy * scale );
11656
11987
  },
11657
11988
 
@@ -11684,7 +12015,7 @@ $.TileSource.prototype = {
11684
12015
  }
11685
12016
 
11686
12017
  callback = function( data ){
11687
- if( typeof(data) === "string" ) {
12018
+ if( typeof (data) === "string" ) {
11688
12019
  data = $.parseXml( data );
11689
12020
  }
11690
12021
  var $TileSource = $.TileSource.determineType( _this, data, url );
@@ -11757,7 +12088,7 @@ $.TileSource.prototype = {
11757
12088
  msg = "HTTP " + xhr.status + " attempting to load TileSource";
11758
12089
  } catch ( e ) {
11759
12090
  var formattedExc;
11760
- if ( typeof( exc ) == "undefined" || !exc.toString ) {
12091
+ if ( typeof ( exc ) == "undefined" || !exc.toString ) {
11761
12092
  formattedExc = "Unknown error";
11762
12093
  } else {
11763
12094
  formattedExc = exc.toString();
@@ -12077,7 +12408,7 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
12077
12408
  *
12078
12409
  * @function
12079
12410
  * @param {Object|XMLDocument} data - the raw configuration
12080
- * @param {String} url - the url the data was retreived from if any.
12411
+ * @param {String} url - the url the data was retrieved from if any.
12081
12412
  * @return {Object} options - A dictionary of keyword arguments sufficient
12082
12413
  * to configure this tile sources constructor.
12083
12414
  */
@@ -12136,6 +12467,10 @@ $.extend( $.DziTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
12136
12467
  yMax,
12137
12468
  i;
12138
12469
 
12470
+ if ((this.minLevel && level < this.minLevel) || (this.maxLevel && level > this.maxLevel)) {
12471
+ return false;
12472
+ }
12473
+
12139
12474
  if ( !rects || !rects.length ) {
12140
12475
  return true;
12141
12476
  }
@@ -12370,6 +12705,8 @@ function configureFromObject( tileSource, configuration ){
12370
12705
  * @memberof OpenSeadragon
12371
12706
  * @extends OpenSeadragon.TileSource
12372
12707
  * @see http://iiif.io/api/image/
12708
+ * @param {String} [options.tileFormat='jpg']
12709
+ * The extension that will be used when requiring tiles.
12373
12710
  */
12374
12711
  $.IIIFTileSource = function( options ){
12375
12712
 
@@ -12383,6 +12720,10 @@ $.IIIFTileSource = function( options ){
12383
12720
 
12384
12721
  options.tileSizePerScaleFactor = {};
12385
12722
 
12723
+ this.tileFormat = this.tileFormat || 'jpg';
12724
+
12725
+ this.version = options.version;
12726
+
12386
12727
  // N.B. 2.0 renamed scale_factors to scaleFactors
12387
12728
  if ( this.tile_width && this.tile_height ) {
12388
12729
  options.tileWidth = this.tile_width;
@@ -12412,7 +12753,7 @@ $.IIIFTileSource = function( options ){
12412
12753
  }
12413
12754
  }
12414
12755
  }
12415
- } else if ( canBeTiled(options.profile) ) {
12756
+ } else if ( canBeTiled(options) ) {
12416
12757
  // use the largest of tileOptions that is smaller than the short dimension
12417
12758
  var shortDim = Math.min( this.height, this.width ),
12418
12759
  tileOptions = [256, 512, 1024],
@@ -12455,7 +12796,8 @@ $.IIIFTileSource = function( options ){
12455
12796
  if (!this.scale_factors) {
12456
12797
  options.maxLevel = Number(Math.ceil(Math.log(Math.max(this.width, this.height), 2)));
12457
12798
  } else {
12458
- options.maxLevel = Math.floor(Math.pow(Math.max.apply(null, this.scale_factors), 0.5));
12799
+ var maxScaleFactor = Math.max.apply(null, this.scale_factors);
12800
+ options.maxLevel = Math.round(Math.log(maxScaleFactor) * Math.LOG2E);
12459
12801
  }
12460
12802
  }
12461
12803
 
@@ -12524,12 +12866,51 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
12524
12866
  var options = configureFromXml10( data );
12525
12867
  options['@context'] = "http://iiif.io/api/image/1.0/context.json";
12526
12868
  options['@id'] = url.replace('/info.xml', '');
12869
+ options.version = 1;
12527
12870
  return options;
12528
- } else if ( !data['@context'] ) {
12529
- data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
12530
- data['@id'] = url.replace('/info.json', '');
12531
- return data;
12532
12871
  } else {
12872
+ if ( !data['@context'] ) {
12873
+ data['@context'] = 'http://iiif.io/api/image/1.0/context.json';
12874
+ data['@id'] = url.replace('/info.json', '');
12875
+ data.version = 1;
12876
+ } else {
12877
+ var context = data['@context'];
12878
+ if (Array.isArray(context)) {
12879
+ for (var i = 0; i < context.length; i++) {
12880
+ if (typeof context[i] === 'string' &&
12881
+ ( /^http:\/\/iiif\.io\/api\/image\/[1-3]\/context\.json$/.test(context[i]) ||
12882
+ context[i] === 'http://library.stanford.edu/iiif/image-api/1.1/context.json' ) ) {
12883
+ context = context[i];
12884
+ break;
12885
+ }
12886
+ }
12887
+ }
12888
+ switch (context) {
12889
+ case 'http://iiif.io/api/image/1/context.json':
12890
+ case 'http://library.stanford.edu/iiif/image-api/1.1/context.json':
12891
+ data.version = 1;
12892
+ break;
12893
+ case 'http://iiif.io/api/image/2/context.json':
12894
+ data.version = 2;
12895
+ break;
12896
+ case 'http://iiif.io/api/image/3/context.json':
12897
+ data.version = 3;
12898
+ break;
12899
+ default:
12900
+ $.console.error('Data has a @context property which contains no known IIIF context URI.');
12901
+ }
12902
+ }
12903
+ if ( !data['@id'] && data['id'] ) {
12904
+ data['@id'] = data['id'];
12905
+ }
12906
+ if(data.preferredFormats) {
12907
+ for (var f = 0; f < data.preferredFormats.length; f++ ) {
12908
+ if ( OpenSeadragon.imageFormatSupported(data.preferredFormats[f]) ) {
12909
+ data.tileFormat = data.preferredFormats[f];
12910
+ break;
12911
+ }
12912
+ }
12913
+ }
12533
12914
  return data;
12534
12915
  }
12535
12916
  },
@@ -12664,6 +13045,8 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
12664
13045
  iiifTileW,
12665
13046
  iiifTileH,
12666
13047
  iiifSize,
13048
+ iiifSizeW,
13049
+ iiifSizeH,
12667
13050
  iiifQuality,
12668
13051
  uri;
12669
13052
 
@@ -12671,29 +13054,52 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
12671
13054
  tileHeight = this.getTileHeight(level);
12672
13055
  iiifTileSizeWidth = Math.ceil( tileWidth / scale );
12673
13056
  iiifTileSizeHeight = Math.ceil( tileHeight / scale );
12674
-
12675
- if ( this['@context'].indexOf('/1.0/context.json') > -1 ||
12676
- this['@context'].indexOf('/1.1/context.json') > -1 ||
12677
- this['@context'].indexOf('/1/context.json') > -1 ) {
12678
- iiifQuality = "native.jpg";
13057
+ if (this.version === 1) {
13058
+ iiifQuality = "native." + this.tileFormat;
12679
13059
  } else {
12680
- iiifQuality = "default.jpg";
13060
+ iiifQuality = "default." + this.tileFormat;
12681
13061
  }
12682
-
12683
13062
  if ( levelWidth < tileWidth && levelHeight < tileHeight ){
12684
- iiifSize = levelWidth + ",";
13063
+ if ( this.version === 2 && levelWidth === this.width ) {
13064
+ iiifSize = "max";
13065
+ } else if ( this.version === 3 && levelWidth === this.width && levelHeight === this.height ) {
13066
+ iiifSize = "max";
13067
+ } else if ( this.version === 3 ) {
13068
+ iiifSize = levelWidth + "," + levelHeight;
13069
+ } else {
13070
+ iiifSize = levelWidth + ",";
13071
+ }
12685
13072
  iiifRegion = 'full';
12686
13073
  } else {
12687
13074
  iiifTileX = x * iiifTileSizeWidth;
12688
13075
  iiifTileY = y * iiifTileSizeHeight;
12689
13076
  iiifTileW = Math.min( iiifTileSizeWidth, this.width - iiifTileX );
12690
13077
  iiifTileH = Math.min( iiifTileSizeHeight, this.height - iiifTileY );
12691
- iiifSize = Math.ceil( iiifTileW * scale ) + ",";
12692
- iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
13078
+ if ( x === 0 && y === 0 && iiifTileW === this.width && iiifTileH === this.height ) {
13079
+ iiifRegion = "full";
13080
+ } else {
13081
+ iiifRegion = [ iiifTileX, iiifTileY, iiifTileW, iiifTileH ].join( ',' );
13082
+ }
13083
+ iiifSizeW = Math.ceil( iiifTileW * scale );
13084
+ iiifSizeH = Math.ceil( iiifTileH * scale );
13085
+ if ( this.version === 2 && iiifSizeW === this.width ) {
13086
+ iiifSize = "max";
13087
+ } else if ( this.version === 3 && iiifSizeW === this.width && iiifSizeH === this.height ) {
13088
+ iiifSize = "max";
13089
+ } else if (this.version === 3) {
13090
+ iiifSize = iiifSizeW + "," + iiifSizeH;
13091
+ } else {
13092
+ iiifSize = iiifSizeW + ",";
13093
+ }
12693
13094
  }
12694
13095
  uri = [ this['@id'], iiifRegion, iiifSize, IIIF_ROTATION, iiifQuality ].join( '/' );
12695
13096
 
12696
13097
  return uri;
13098
+ },
13099
+
13100
+ __testonly__: {
13101
+ canBeTiled: canBeTiled,
13102
+ constructLevels: constructLevels
12697
13103
  }
12698
13104
 
12699
13105
  });
@@ -12701,17 +13107,27 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
12701
13107
  /**
12702
13108
  * Determine whether arbitrary tile requests can be made against a service with the given profile
12703
13109
  * @function
12704
- * @param {object} profile - IIIF profile object
13110
+ * @param {array} profile - IIIF profile array
12705
13111
  * @throws {Error}
12706
13112
  */
12707
- function canBeTiled (profile ) {
13113
+ function canBeTiled ( options ) {
12708
13114
  var level0Profiles = [
12709
13115
  "http://library.stanford.edu/iiif/image-api/compliance.html#level0",
12710
13116
  "http://library.stanford.edu/iiif/image-api/1.1/compliance.html#level0",
12711
- "http://iiif.io/api/image/2/level0.json"
13117
+ "http://iiif.io/api/image/2/level0.json",
13118
+ "level0",
13119
+ "https://iiif.io/api/image/3/level0.json"
12712
13120
  ];
12713
- var isLevel0 = (level0Profiles.indexOf(profile[0]) != -1);
12714
- return !isLevel0 || (profile.indexOf("sizeByW") != -1);
13121
+ var profileLevel = Array.isArray(options.profile) ? options.profile[0] : options.profile;
13122
+ var isLevel0 = (level0Profiles.indexOf(profileLevel) !== -1);
13123
+ var hasCanoncicalSizeFeature = false;
13124
+ if ( options.version === 2 && options.profile.length > 1 && options.profile[1].supports ) {
13125
+ hasCanoncicalSizeFeature = options.profile[1].supports.indexOf( "sizeByW" ) !== -1;
13126
+ }
13127
+ if ( options.version === 3 && options.extraFeatures ) {
13128
+ hasCanoncicalSizeFeature = options.extraFeatures.indexOf( "sizeByWh" ) !== -1;
13129
+ }
13130
+ return !isLevel0 || hasCanoncicalSizeFeature;
12715
13131
  }
12716
13132
 
12717
13133
  /**
@@ -12724,7 +13140,9 @@ $.extend( $.IIIFTileSource.prototype, $.TileSource.prototype, /** @lends OpenSea
12724
13140
  var levels = [];
12725
13141
  for(var i = 0; i < options.sizes.length; i++) {
12726
13142
  levels.push({
12727
- url: options['@id'] + '/full/' + options.sizes[i].width + ',/0/default.jpg',
13143
+ url: options['@id'] + '/full/' + options.sizes[i].width + ',' +
13144
+ (options.version === 3 ? options.sizes[i].height : '') +
13145
+ '/0/default.' + options.tileFormat,
12728
13146
  width: options.sizes[i].width,
12729
13147
  height: options.sizes[i].height
12730
13148
  });
@@ -12910,7 +13328,7 @@ $.extend( $.OsmTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
12910
13328
  *
12911
13329
  * @function
12912
13330
  * @param {Object} data - the raw configuration
12913
- * @param {String} url - the url the data was retreived from if any.
13331
+ * @param {String} url - the url the data was retrieved from if any.
12914
13332
  * @return {Object} options - A dictionary of keyword arguments sufficient
12915
13333
  * to configure this tile sources constructor.
12916
13334
  */
@@ -13045,7 +13463,7 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
13045
13463
  *
13046
13464
  * @function
13047
13465
  * @param {Object} data - the raw configuration
13048
- * @param {String} url - the url the data was retreived from if any.
13466
+ * @param {String} url - the url the data was retrieved from if any.
13049
13467
  * @return {Object} options - A dictionary of keyword arguments sufficient
13050
13468
  * to configure this tile sources constructor.
13051
13469
  */
@@ -13187,7 +13605,7 @@ $.extend( $.TmsTileSource.prototype, $.TileSource.prototype, /** @lends OpenSead
13187
13605
  *
13188
13606
  * @function
13189
13607
  * @param {Object} data - the raw configuration
13190
- * @param {String} url - the url the data was retreived from if any.
13608
+ * @param {String} url - the url the data was retrieved from if any.
13191
13609
  * @return {Object} options - A dictionary of keyword arguments sufficient
13192
13610
  * to configure this tile sources constructor.
13193
13611
  */
@@ -13337,7 +13755,7 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
13337
13755
  *
13338
13756
  * @function
13339
13757
  * @param {Object|XMLDocument} configuration - the raw configuration
13340
- * @param {String} dataUrl - the url the data was retreived from if any.
13758
+ * @param {String} dataUrl - the url the data was retrieved from if any.
13341
13759
  * @return {Object} options - A dictionary of keyword arguments sufficient
13342
13760
  * to configure this tile sources constructor.
13343
13761
  */
@@ -13406,7 +13824,7 @@ $.extend( $.LegacyTileSource.prototype, $.TileSource.prototype, /** @lends OpenS
13406
13824
  } );
13407
13825
 
13408
13826
  /**
13409
- * This method removes any files from the Array which dont conform to our
13827
+ * This method removes any files from the Array which don't conform to our
13410
13828
  * basic requirements for a 'level' in the LegacyTileSource.
13411
13829
  * @private
13412
13830
  * @inner
@@ -13595,7 +14013,7 @@ function configureFromObject( tileSource, configuration ){
13595
14013
  *
13596
14014
  * @function
13597
14015
  * @param {Object} options - the options
13598
- * @param {String} dataUrl - the url the image was retreived from, if any.
14016
+ * @param {String} dataUrl - the url the image was retrieved from, if any.
13599
14017
  * @return {Object} options - A dictionary of keyword arguments sufficient
13600
14018
  * to configure this tile sources constructor.
13601
14019
  */
@@ -13705,7 +14123,7 @@ function configureFromObject( tileSource, configuration ){
13705
14123
 
13706
14124
  // private
13707
14125
  //
13708
- // Builds the differents levels of the pyramid if possible
14126
+ // Builds the different levels of the pyramid if possible
13709
14127
  // (i.e. if canvas API enabled and no canvas tainting issue).
13710
14128
  _buildLevels: function () {
13711
14129
  var levels = [{
@@ -14394,11 +14812,11 @@ $.ButtonGroup = function( options ) {
14394
14812
 
14395
14813
  // TODO What if there IS an options.group specified?
14396
14814
  if( !options.group ){
14397
- this.label = $.makeNeutralElement( "label" );
14815
+ this.element.style.display = "inline-block";
14816
+ //this.label = $.makeNeutralElement( "label" );
14398
14817
  //TODO: support labels for ButtonGroups
14399
14818
  //this.label.innerHTML = this.labelText;
14400
- this.element.style.display = "inline-block";
14401
- this.element.appendChild( this.label );
14819
+ //this.element.appendChild( this.label );
14402
14820
  for ( i = 0; i < buttons.length; i++ ) {
14403
14821
  this.element.appendChild( buttons[ i ].element );
14404
14822
  }
@@ -14407,7 +14825,7 @@ $.ButtonGroup = function( options ) {
14407
14825
  $.setElementTouchActionNone( this.element );
14408
14826
 
14409
14827
  /**
14410
- * Tracks mouse/touch/key events accross the group of buttons.
14828
+ * Tracks mouse/touch/key events across the group of buttons.
14411
14829
  * @member {OpenSeadragon.MouseTracker} tracker
14412
14830
  * @memberof OpenSeadragon.ButtonGroup#
14413
14831
  */
@@ -14519,27 +14937,32 @@ $.Rect = function(x, y, width, height, degrees) {
14519
14937
  * @member {Number} x
14520
14938
  * @memberof OpenSeadragon.Rect#
14521
14939
  */
14522
- this.x = typeof(x) === "number" ? x : 0;
14940
+ this.x = typeof (x) === "number" ? x : 0;
14523
14941
  /**
14524
14942
  * The vector component 'y'.
14525
14943
  * @member {Number} y
14526
14944
  * @memberof OpenSeadragon.Rect#
14527
14945
  */
14528
- this.y = typeof(y) === "number" ? y : 0;
14946
+ this.y = typeof (y) === "number" ? y : 0;
14529
14947
  /**
14530
14948
  * The vector component 'width'.
14531
14949
  * @member {Number} width
14532
14950
  * @memberof OpenSeadragon.Rect#
14533
14951
  */
14534
- this.width = typeof(width) === "number" ? width : 0;
14952
+ this.width = typeof (width) === "number" ? width : 0;
14535
14953
  /**
14536
14954
  * The vector component 'height'.
14537
14955
  * @member {Number} height
14538
14956
  * @memberof OpenSeadragon.Rect#
14539
14957
  */
14540
- this.height = typeof(height) === "number" ? height : 0;
14958
+ this.height = typeof (height) === "number" ? height : 0;
14541
14959
 
14542
- this.degrees = typeof(degrees) === "number" ? degrees : 0;
14960
+ /**
14961
+ * The rotation of the rectangle, in degrees.
14962
+ * @member {Number} degrees
14963
+ * @memberof OpenSeadragon.Rect#
14964
+ */
14965
+ this.degrees = typeof (degrees) === "number" ? degrees : 0;
14543
14966
 
14544
14967
  // Normalizes the rectangle.
14545
14968
  this.degrees = $.positiveModulo(this.degrees, 360);
@@ -15073,7 +15496,7 @@ var THIS = {};
15073
15496
  * TODO: The difficult part of this feature is figuring out how to express
15074
15497
  * this functionality as a combination of the functionality already
15075
15498
  * provided by Drawer, Viewport, TileSource, and Navigator. It may
15076
- * require better abstraction at those points in order to effeciently
15499
+ * require better abstraction at those points in order to efficiently
15077
15500
  * reuse those paradigms.
15078
15501
  */
15079
15502
  /**
@@ -15256,7 +15679,7 @@ $.extend( $.ReferenceStrip.prototype, $.EventSource.prototype, $.Viewer.prototyp
15256
15679
  * @function
15257
15680
  */
15258
15681
  setFocus: function ( page ) {
15259
- var element = $.getElement( this.element.id + '-' + page ),
15682
+ var element = this.element.querySelector('#' + this.element.id + '-' + page ),
15260
15683
  viewerSize = $.getElementSize( this.viewer.canvas ),
15261
15684
  scrollWidth = Number( this.element.style.width.replace( 'px', '' ) ),
15262
15685
  scrollHeight = Number( this.element.style.height.replace( 'px', '' ) ),
@@ -15469,7 +15892,10 @@ function loadPanels( strip, viewerSize, scroll ) {
15469
15892
  showSequenceControl: false,
15470
15893
  immediateRender: true,
15471
15894
  blendTime: 0,
15472
- animationTime: 0
15895
+ animationTime: 0,
15896
+ loadTilesWithAjax: strip.viewer.loadTilesWithAjax,
15897
+ ajaxHeaders: strip.viewer.ajaxHeaders,
15898
+ useCanvas: strip.useCanvas
15473
15899
  } );
15474
15900
 
15475
15901
  miniViewer.displayRegion = $.makeNeutralElement( "div" );
@@ -15757,7 +16183,7 @@ $.extend( $.DisplayRect.prototype, $.Rect.prototype );
15757
16183
  $.Spring = function( options ) {
15758
16184
  var args = arguments;
15759
16185
 
15760
- if( typeof( options ) != 'object' ){
16186
+ if( typeof ( options ) != 'object' ){
15761
16187
  //allows backward compatible use of ( initialValue, config ) as
15762
16188
  //constructor parameters
15763
16189
  options = {
@@ -16053,7 +16479,7 @@ ImageJob.prototype = {
16053
16479
  };
16054
16480
 
16055
16481
  this.jobId = window.setTimeout(function(){
16056
- self.errorMsg = "Image load exceeded timeout";
16482
+ self.errorMsg = "Image load exceeded timeout (" + self.timeout + " ms)";
16057
16483
  self.finish(false);
16058
16484
  }, this.timeout);
16059
16485
 
@@ -16278,7 +16704,7 @@ function completeJob(loader, job, callback) {
16278
16704
  * @param {Number} level The zoom level this tile belongs to.
16279
16705
  * @param {Number} x The vector component 'x'.
16280
16706
  * @param {Number} y The vector component 'y'.
16281
- * @param {OpenSeadragon.Point} bounds Where this tile fits, in normalized
16707
+ * @param {OpenSeadragon.Rect} bounds Where this tile fits, in normalized
16282
16708
  * coordinates.
16283
16709
  * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
16284
16710
  * this tile failed to load? )
@@ -16287,8 +16713,11 @@ function completeJob(loader, job, callback) {
16287
16713
  * is provided directly by the tile source.
16288
16714
  * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
16289
16715
  * @param {Object} ajaxHeaders The headers to send with this tile's AJAX request (if applicable).
16716
+ * @param {OpenSeadragon.Rect} sourceBounds The portion of the tile to use as the source of the
16717
+ * drawing operation, in pixels. Note that this only works when drawing with canvas; when drawing
16718
+ * with HTML the entire tile is always used.
16290
16719
  */
16291
- $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders) {
16720
+ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, ajaxHeaders, sourceBounds) {
16292
16721
  /**
16293
16722
  * The zoom level this tile belongs to.
16294
16723
  * @member {Number} level
@@ -16313,6 +16742,13 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
16313
16742
  * @memberof OpenSeadragon.Tile#
16314
16743
  */
16315
16744
  this.bounds = bounds;
16745
+ /**
16746
+ * The portion of the tile to use as the source of the drawing operation, in pixels. Note that
16747
+ * this only works when drawing with canvas; when drawing with HTML the entire tile is always used.
16748
+ * @member {OpenSeadragon.Rect} sourceBounds
16749
+ * @memberof OpenSeadragon.Tile#
16750
+ */
16751
+ this.sourceBounds = sourceBounds;
16316
16752
  /**
16317
16753
  * Is this tile a part of a sparse image? Also has this tile failed to load?
16318
16754
  * @member {Boolean} exists
@@ -16437,12 +16873,27 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
16437
16873
  * @memberof OpenSeadragon.Tile#
16438
16874
  */
16439
16875
  this.beingDrawn = false;
16876
+
16440
16877
  /**
16441
16878
  * Timestamp the tile was last touched.
16442
16879
  * @member {Number} lastTouchTime
16443
16880
  * @memberof OpenSeadragon.Tile#
16444
16881
  */
16445
16882
  this.lastTouchTime = 0;
16883
+
16884
+ /**
16885
+ * Whether this tile is in the right-most column for its level.
16886
+ * @member {Boolean} isRightMost
16887
+ * @memberof OpenSeadragon.Tile#
16888
+ */
16889
+ this.isRightMost = false;
16890
+
16891
+ /**
16892
+ * Whether this tile is in the bottom-most row for its level.
16893
+ * @member {Boolean} isBottomMost
16894
+ * @memberof OpenSeadragon.Tile#
16895
+ */
16896
+ this.isBottomMost = false;
16446
16897
  };
16447
16898
 
16448
16899
  /** @lends OpenSeadragon.Tile.prototype */
@@ -16569,10 +17020,10 @@ $.Tile.prototype = {
16569
17020
  //clearing only the inside of the rectangle occupied
16570
17021
  //by the png prevents edge flikering
16571
17022
  context.clearRect(
16572
- position.x + 1,
16573
- position.y + 1,
16574
- size.x - 2,
16575
- size.y - 2
17023
+ position.x,
17024
+ position.y,
17025
+ size.x,
17026
+ size.y
16576
17027
  );
16577
17028
  }
16578
17029
 
@@ -16580,12 +17031,21 @@ $.Tile.prototype = {
16580
17031
  // changes as we are rendering the image
16581
17032
  drawingHandler({context: context, tile: this, rendered: rendered});
16582
17033
 
17034
+ var sourceWidth, sourceHeight;
17035
+ if (this.sourceBounds) {
17036
+ sourceWidth = Math.min(this.sourceBounds.width, rendered.canvas.width);
17037
+ sourceHeight = Math.min(this.sourceBounds.height, rendered.canvas.height);
17038
+ } else {
17039
+ sourceWidth = rendered.canvas.width;
17040
+ sourceHeight = rendered.canvas.height;
17041
+ }
17042
+
16583
17043
  context.drawImage(
16584
17044
  rendered.canvas,
16585
17045
  0,
16586
17046
  0,
16587
- rendered.canvas.width,
16588
- rendered.canvas.height,
17047
+ sourceWidth,
17048
+ sourceHeight,
16589
17049
  position.x,
16590
17050
  position.y,
16591
17051
  size.x,
@@ -16755,7 +17215,7 @@ $.Tile.prototype = {
16755
17215
  * Defines what part of the overlay should be at the specified options.location
16756
17216
  * @param {OpenSeadragon.Overlay.OnDrawCallback} [options.onDraw]
16757
17217
  * @param {Boolean} [options.checkResize=true] Set to false to avoid to
16758
- * check the size of the overlay everytime it is drawn in the directions
17218
+ * check the size of the overlay every time it is drawn in the directions
16759
17219
  * which are not scaled. It will improve performances but will cause a
16760
17220
  * misalignment if the overlay size changes.
16761
17221
  * @param {Number} [options.width] The width of the overlay in viewport
@@ -17189,7 +17649,7 @@ $.Drawer = function( options ) {
17189
17649
 
17190
17650
  $.console.assert( options.viewer, "[Drawer] options.viewer is required" );
17191
17651
 
17192
- //backward compatibility for positional args while prefering more
17652
+ //backward compatibility for positional args while preferring more
17193
17653
  //idiomatic javascript options object as the only argument
17194
17654
  var args = arguments;
17195
17655
 
@@ -17210,7 +17670,7 @@ $.Drawer = function( options ) {
17210
17670
 
17211
17671
  this.viewer = options.viewer;
17212
17672
  this.viewport = options.viewport;
17213
- this.debugGridColor = options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
17673
+ this.debugGridColor = typeof options.debugGridColor === 'string' ? [options.debugGridColor] : options.debugGridColor || $.DEFAULT_SETTINGS.debugGridColor;
17214
17674
  if (options.opacity) {
17215
17675
  $.console.error( "[Drawer] options.opacity is no longer accepted; set the opacity on the TiledImage instead" );
17216
17676
  }
@@ -17271,6 +17731,10 @@ $.Drawer = function( options ) {
17271
17731
  // explicit left-align
17272
17732
  this.container.style.textAlign = "left";
17273
17733
  this.container.appendChild( this.canvas );
17734
+
17735
+ // Image smoothing for canvas rendering (only if canvas is used).
17736
+ // Canvas default is "true", so this will only be changed if user specified "false".
17737
+ this._imageSmoothingEnabled = true;
17274
17738
  };
17275
17739
 
17276
17740
  /** @lends OpenSeadragon.Drawer.prototype */
@@ -17303,6 +17767,41 @@ $.Drawer.prototype = {
17303
17767
  return this;
17304
17768
  },
17305
17769
 
17770
+ /**
17771
+ * This function converts the given point from to the drawer coordinate by
17772
+ * multiplying it with the pixel density.
17773
+ * This function does not take rotation into account, thus assuming provided
17774
+ * point is at 0 degree.
17775
+ * @param {OpenSeadragon.Point} point - the pixel point to convert
17776
+ */
17777
+ viewportCoordToDrawerCoord: function(point) {
17778
+ var vpPoint = this.viewport.pixelFromPointNoRotate(point, true);
17779
+ return new $.Point(
17780
+ vpPoint.x * $.pixelDensityRatio,
17781
+ vpPoint.y * $.pixelDensityRatio
17782
+ );
17783
+ },
17784
+
17785
+ /**
17786
+ * This function will create multiple polygon paths on the drawing context by provided polygons,
17787
+ * then clip the context to the paths.
17788
+ * @param {(OpenSeadragon.Point[])[]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point
17789
+ * @param {Boolean} useSketch - Whether to use the sketch canvas or not.
17790
+ */
17791
+ clipWithPolygons: function (polygons, useSketch) {
17792
+ if (!this.useCanvas) {
17793
+ return;
17794
+ }
17795
+ var context = this._getContext(useSketch);
17796
+ context.beginPath();
17797
+ polygons.forEach(function (polygon) {
17798
+ polygon.forEach(function (coord, i) {
17799
+ context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y);
17800
+ });
17801
+ });
17802
+ context.clip();
17803
+ },
17804
+
17306
17805
  /**
17307
17806
  * Set the opacity of the drawer.
17308
17807
  * @param {Number} opacity
@@ -17390,10 +17889,12 @@ $.Drawer.prototype = {
17390
17889
  this.canvas.height != viewportSize.y ) {
17391
17890
  this.canvas.width = viewportSize.x;
17392
17891
  this.canvas.height = viewportSize.y;
17892
+ this._updateImageSmoothingEnabled(this.context);
17393
17893
  if ( this.sketchCanvas !== null ) {
17394
17894
  var sketchCanvasSize = this._calculateSketchCanvasSize();
17395
17895
  this.sketchCanvas.width = sketchCanvasSize.x;
17396
17896
  this.sketchCanvas.height = sketchCanvasSize.y;
17897
+ this._updateImageSmoothingEnabled(this.sketchContext);
17397
17898
  }
17398
17899
  }
17399
17900
  this._clear();
@@ -17479,6 +17980,7 @@ $.Drawer.prototype = {
17479
17980
  self.sketchCanvas.height = sketchCanvasSize.y;
17480
17981
  });
17481
17982
  }
17983
+ this._updateImageSmoothingEnabled(this.sketchContext);
17482
17984
  }
17483
17985
  context = this.sketchContext;
17484
17986
  }
@@ -17631,12 +18133,13 @@ $.Drawer.prototype = {
17631
18133
  return;
17632
18134
  }
17633
18135
 
18136
+ var colorIndex = this.viewer.world.getIndexOfItem(tiledImage) % this.debugGridColor.length;
17634
18137
  var context = this.context;
17635
18138
  context.save();
17636
18139
  context.lineWidth = 2 * $.pixelDensityRatio;
17637
18140
  context.font = 'small-caps bold ' + (13 * $.pixelDensityRatio) + 'px arial';
17638
- context.strokeStyle = this.debugGridColor;
17639
- context.fillStyle = this.debugGridColor;
18141
+ context.strokeStyle = this.debugGridColor[colorIndex];
18142
+ context.fillStyle = this.debugGridColor[colorIndex];
17640
18143
 
17641
18144
  if ( this.viewport.degrees !== 0 ) {
17642
18145
  this._offsetForRotation({degrees: this.viewport.degrees});
@@ -17648,6 +18151,11 @@ $.Drawer.prototype = {
17648
18151
  tiledImage._getRotationPoint(true), true)
17649
18152
  });
17650
18153
  }
18154
+ if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
18155
+ if(tiledImage._drawer.viewer.viewport.getFlip()) {
18156
+ tiledImage._drawer._flip();
18157
+ }
18158
+ }
17651
18159
 
17652
18160
  context.strokeRect(
17653
18161
  tile.position.x * $.pixelDensityRatio,
@@ -17713,6 +18221,13 @@ $.Drawer.prototype = {
17713
18221
  if (tiledImage.getRotation(true) % 360 !== 0) {
17714
18222
  this._restoreRotationChanges();
17715
18223
  }
18224
+
18225
+ if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
18226
+ if(tiledImage._drawer.viewer.viewport.getFlip()) {
18227
+ tiledImage._drawer._flip();
18228
+ }
18229
+ }
18230
+
17716
18231
  context.restore();
17717
18232
  },
17718
18233
 
@@ -17722,8 +18237,8 @@ $.Drawer.prototype = {
17722
18237
  var context = this.context;
17723
18238
  context.save();
17724
18239
  context.lineWidth = 2 * $.pixelDensityRatio;
17725
- context.strokeStyle = this.debugGridColor;
17726
- context.fillStyle = this.debugGridColor;
18240
+ context.strokeStyle = this.debugGridColor[0];
18241
+ context.fillStyle = this.debugGridColor[0];
17727
18242
 
17728
18243
  context.strokeRect(
17729
18244
  rect.x * $.pixelDensityRatio,
@@ -17736,6 +18251,28 @@ $.Drawer.prototype = {
17736
18251
  }
17737
18252
  },
17738
18253
 
18254
+ /**
18255
+ * Turns image smoothing on or off for this viewer. Note: Ignored in some (especially older) browsers that do not support this property.
18256
+ *
18257
+ * @function
18258
+ * @param {Boolean} [imageSmoothingEnabled] - Whether or not the image is
18259
+ * drawn smoothly on the canvas; see imageSmoothingEnabled in
18260
+ * {@link OpenSeadragon.Options} for more explanation.
18261
+ */
18262
+ setImageSmoothingEnabled: function(imageSmoothingEnabled){
18263
+ if ( this.useCanvas ) {
18264
+ this._imageSmoothingEnabled = imageSmoothingEnabled;
18265
+ this._updateImageSmoothingEnabled(this.context);
18266
+ this.viewer.forceRedraw();
18267
+ }
18268
+ },
18269
+
18270
+ // private
18271
+ _updateImageSmoothingEnabled: function(context){
18272
+ context.msImageSmoothingEnabled = this._imageSmoothingEnabled;
18273
+ context.imageSmoothingEnabled = this._imageSmoothingEnabled;
18274
+ },
18275
+
17739
18276
  /**
17740
18277
  * Get the canvas size
17741
18278
  * @param {Boolean} sketch If set to true return the size of the sketch canvas
@@ -17760,10 +18297,28 @@ $.Drawer.prototype = {
17760
18297
  context.save();
17761
18298
 
17762
18299
  context.translate(point.x, point.y);
17763
- context.rotate(Math.PI / 180 * options.degrees);
18300
+ if(this.viewer.viewport.flipped){
18301
+ context.rotate(Math.PI / 180 * -options.degrees);
18302
+ context.scale(-1, 1);
18303
+ } else{
18304
+ context.rotate(Math.PI / 180 * options.degrees);
18305
+ }
17764
18306
  context.translate(-point.x, -point.y);
17765
18307
  },
17766
18308
 
18309
+ // private
18310
+ _flip: function(options) {
18311
+ options = options || {};
18312
+ var point = options.point ?
18313
+ options.point.times($.pixelDensityRatio) :
18314
+ this.getCanvasCenter();
18315
+ var context = this._getContext(options.useSketch);
18316
+
18317
+ context.translate(point.x, 0);
18318
+ context.scale(-1, 1);
18319
+ context.translate(-point.x, 0);
18320
+ },
18321
+
17767
18322
  // private
17768
18323
  _restoreRotationChanges: function(useSketch) {
17769
18324
  var context = this._getContext(useSketch);
@@ -17775,8 +18330,9 @@ $.Drawer.prototype = {
17775
18330
  var pixelDensityRatio = $.pixelDensityRatio;
17776
18331
  var viewportSize = this.viewport.getContainerSize();
17777
18332
  return {
17778
- x: viewportSize.x * pixelDensityRatio,
17779
- y: viewportSize.y * pixelDensityRatio
18333
+ // canvas width and height are integers
18334
+ x: Math.round(viewportSize.x * pixelDensityRatio),
18335
+ y: Math.round(viewportSize.y * pixelDensityRatio)
17780
18336
  };
17781
18337
  },
17782
18338
 
@@ -17859,7 +18415,7 @@ $.Drawer.prototype = {
17859
18415
  */
17860
18416
  $.Viewport = function( options ) {
17861
18417
 
17862
- //backward compatibility for positional args while prefering more
18418
+ //backward compatibility for positional args while preferring more
17863
18419
  //idiomatic javascript options object as the only argument
17864
18420
  var args = arguments;
17865
18421
  if (args.length && args[0] instanceof $.Point) {
@@ -17909,6 +18465,7 @@ $.Viewport = function( options ) {
17909
18465
  minZoomLevel: $.DEFAULT_SETTINGS.minZoomLevel,
17910
18466
  maxZoomLevel: $.DEFAULT_SETTINGS.maxZoomLevel,
17911
18467
  degrees: $.DEFAULT_SETTINGS.degrees,
18468
+ flipped: $.DEFAULT_SETTINGS.flipped,
17912
18469
  homeFillsViewer: $.DEFAULT_SETTINGS.homeFillsViewer
17913
18470
 
17914
18471
  }, options );
@@ -18676,13 +19233,13 @@ $.Viewport.prototype = {
18676
19233
  /**
18677
19234
  * Rotates this viewport to the angle specified.
18678
19235
  * @function
19236
+ * @param {Number} degrees The degrees to set the rotation to.
18679
19237
  * @return {OpenSeadragon.Viewport} Chainable.
18680
19238
  */
18681
19239
  setRotation: function(degrees) {
18682
19240
  if (!this.viewer || !this.viewer.drawer.canRotate()) {
18683
19241
  return this;
18684
19242
  }
18685
-
18686
19243
  this.degrees = $.positiveModulo(degrees, 360);
18687
19244
  this._setContentBounds(
18688
19245
  this.viewer.world.getHomeBounds(),
@@ -19320,7 +19877,58 @@ $.Viewport.prototype = {
19320
19877
  var scale = this._contentBoundsNoRotate.width;
19321
19878
  var viewportToImageZoomRatio = (imageWidth / containerWidth) / scale;
19322
19879
  return imageZoom * viewportToImageZoomRatio;
19880
+ },
19881
+
19882
+ /**
19883
+ * Toggles flip state and demands a new drawing on navigator and viewer objects.
19884
+ * @function
19885
+ * @return {OpenSeadragon.Viewport} Chainable.
19886
+ */
19887
+ toggleFlip: function() {
19888
+ this.setFlip(!this.getFlip());
19889
+ return this;
19890
+ },
19891
+
19892
+ /**
19893
+ * Get flip state stored on viewport.
19894
+ * @function
19895
+ * @return {Boolean} Flip state.
19896
+ */
19897
+ getFlip: function() {
19898
+ return this.flipped;
19899
+ },
19900
+
19901
+ /**
19902
+ * Sets flip state according to the state input argument.
19903
+ * @function
19904
+ * @param {Boolean} state - Flip state to set.
19905
+ * @return {OpenSeadragon.Viewport} Chainable.
19906
+ */
19907
+ setFlip: function( state ) {
19908
+ if ( this.flipped === state ) {
19909
+ return this;
19910
+ }
19911
+
19912
+ this.flipped = state;
19913
+ if(this.viewer.navigator){
19914
+ this.viewer.navigator.setFlip(this.getFlip());
19915
+ }
19916
+ this.viewer.forceRedraw();
19917
+
19918
+ /**
19919
+ * Raised when flip state has been changed.
19920
+ *
19921
+ * @event flip
19922
+ * @memberof OpenSeadragon.Viewer
19923
+ * @type {object}
19924
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised the event.
19925
+ * @property {Number} flipped - The flip state after this change.
19926
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
19927
+ */
19928
+ this.viewer.raiseEvent('flip', {"flipped": state});
19929
+ return this;
19323
19930
  }
19931
+
19324
19932
  };
19325
19933
 
19326
19934
  }( OpenSeadragon ));
@@ -19362,8 +19970,8 @@ $.Viewport.prototype = {
19362
19970
  (function( $ ){
19363
19971
 
19364
19972
  /**
19365
- * You shouldn't have to create a TiledImage directly; use {@link OpenSeadragon.Viewer#open}
19366
- * or {@link OpenSeadragon.Viewer#addTiledImage} instead.
19973
+ * You shouldn't have to create a TiledImage instance directly; get it asynchronously by
19974
+ * using {@link OpenSeadragon.Viewer#open} or {@link OpenSeadragon.Viewer#addTiledImage} instead.
19367
19975
  * @class TiledImage
19368
19976
  * @memberof OpenSeadragon
19369
19977
  * @extends OpenSeadragon.EventSource
@@ -19412,7 +20020,11 @@ $.Viewport.prototype = {
19412
20020
  */
19413
20021
  $.TiledImage = function( options ) {
19414
20022
  var _this = this;
19415
-
20023
+ /**
20024
+ * The {@link OpenSeadragon.TileSource} that defines this TiledImage.
20025
+ * @member {OpenSeadragon.TileSource} source
20026
+ * @memberof OpenSeadragon.TiledImage#
20027
+ */
19416
20028
  $.console.assert( options.tileCache, "[TiledImage] options.tileCache is required" );
19417
20029
  $.console.assert( options.drawer, "[TiledImage] options.drawer is required" );
19418
20030
  $.console.assert( options.viewer, "[TiledImage] options.viewer is required" );
@@ -19638,6 +20250,10 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19638
20250
  this._updateViewport();
19639
20251
  this._midDraw = false;
19640
20252
  }
20253
+ // Images with opacity 0 should not need to be drawn in future. this._needsDraw = false is set in this._updateViewport() for other images.
20254
+ else {
20255
+ this._needsDraw = false;
20256
+ }
19641
20257
  },
19642
20258
 
19643
20259
  /**
@@ -19993,6 +20609,58 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
19993
20609
  this._setScale(height / this.normHeight, immediately);
19994
20610
  },
19995
20611
 
20612
+ /**
20613
+ * Sets an array of polygons to crop the TiledImage during draw tiles.
20614
+ * The render function will use the default non-zero winding rule.
20615
+ * @param Polygons represented in an array of point object in image coordinates.
20616
+ * Example format: [
20617
+ * [{x: 197, y:172}, {x: 226, y:172}, {x: 226, y:198}, {x: 197, y:198}], // First polygon
20618
+ * [{x: 328, y:200}, {x: 330, y:199}, {x: 332, y:201}, {x: 329, y:202}] // Second polygon
20619
+ * [{x: 321, y:201}, {x: 356, y:205}, {x: 341, y:250}] // Third polygon
20620
+ * ]
20621
+ */
20622
+ setCroppingPolygons: function( polygons ) {
20623
+
20624
+ var isXYObject = function(obj) {
20625
+ return obj instanceof $.Point || (typeof obj.x === 'number' && typeof obj.y === 'number');
20626
+ };
20627
+
20628
+ var objectToSimpleXYObject = function(objs) {
20629
+ return objs.map(function(obj) {
20630
+ try {
20631
+ if (isXYObject(obj)) {
20632
+ return { x: obj.x, y: obj.y };
20633
+ } else {
20634
+ throw new Error();
20635
+ }
20636
+ } catch(e) {
20637
+ throw new Error('A Provided cropping polygon point is not supported');
20638
+ }
20639
+ });
20640
+ };
20641
+
20642
+ try {
20643
+ if (!$.isArray(polygons)) {
20644
+ throw new Error('Provided cropping polygon is not an array');
20645
+ }
20646
+ this._croppingPolygons = polygons.map(function(polygon){
20647
+ return objectToSimpleXYObject(polygon);
20648
+ });
20649
+ } catch (e) {
20650
+ $.console.error('[TiledImage.setCroppingPolygons] Cropping polygon format not supported');
20651
+ $.console.error(e);
20652
+ this._croppingPolygons = null;
20653
+ }
20654
+ },
20655
+
20656
+ /**
20657
+ * Resets the cropping polygons, thus next render will remove all cropping
20658
+ * polygon effects.
20659
+ */
20660
+ resetCroppingPolygons: function() {
20661
+ this._croppingPolygons = null;
20662
+ },
20663
+
19996
20664
  /**
19997
20665
  * Positions and scales the TiledImage to fit in the specified bounds.
19998
20666
  * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change
@@ -20176,8 +20844,8 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
20176
20844
  },
20177
20845
 
20178
20846
  /**
20179
- * @private
20180
20847
  * Get the point around which this tiled image is rotated
20848
+ * @private
20181
20849
  * @param {Boolean} current True for current rotation point, false for target.
20182
20850
  * @returns {OpenSeadragon.Point}
20183
20851
  */
@@ -20291,6 +20959,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
20291
20959
 
20292
20960
  // Calculations for the interval of levels to draw
20293
20961
  // can return invalid intervals; fix that here if necessary
20962
+ highestLevel = Math.max(highestLevel, this.source.minLevel || 0);
20294
20963
  lowestLevel = Math.min(lowestLevel, highestLevel);
20295
20964
  return {
20296
20965
  lowestLevel: lowestLevel,
@@ -20695,6 +21364,7 @@ function getTile(
20695
21364
  var xMod,
20696
21365
  yMod,
20697
21366
  bounds,
21367
+ sourceBounds,
20698
21368
  exists,
20699
21369
  url,
20700
21370
  ajaxHeaders,
@@ -20712,6 +21382,7 @@ function getTile(
20712
21382
  xMod = ( numTiles.x + ( x % numTiles.x ) ) % numTiles.x;
20713
21383
  yMod = ( numTiles.y + ( y % numTiles.y ) ) % numTiles.y;
20714
21384
  bounds = tileSource.getTileBounds( level, xMod, yMod );
21385
+ sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true );
20715
21386
  exists = tileSource.tileExists( level, xMod, yMod );
20716
21387
  url = tileSource.getTileUrl( level, xMod, yMod );
20717
21388
 
@@ -20732,7 +21403,7 @@ function getTile(
20732
21403
  bounds.x += ( x - xMod ) / numTiles.x;
20733
21404
  bounds.y += (worldHeight / worldWidth) * (( y - yMod ) / numTiles.y);
20734
21405
 
20735
- tilesMatrix[ level ][ x ][ y ] = new $.Tile(
21406
+ tile = new $.Tile(
20736
21407
  level,
20737
21408
  x,
20738
21409
  y,
@@ -20741,8 +21412,19 @@ function getTile(
20741
21412
  url,
20742
21413
  context2D,
20743
21414
  tiledImage.loadTilesWithAjax,
20744
- ajaxHeaders
21415
+ ajaxHeaders,
21416
+ sourceBounds
20745
21417
  );
21418
+
21419
+ if (xMod === numTiles.x - 1) {
21420
+ tile.isRightMost = true;
21421
+ }
21422
+
21423
+ if (yMod === numTiles.y - 1) {
21424
+ tile.isBottomMost = true;
21425
+ }
21426
+
21427
+ tilesMatrix[ level ][ x ][ y ] = tile;
20746
21428
  }
20747
21429
 
20748
21430
  tile = tilesMatrix[ level ][ x ][ y ];
@@ -20929,6 +21611,14 @@ function positionTile( tile, overlap, viewport, viewportCenter, levelVisibility,
20929
21611
  sizeC = sizeC.plus( new $.Point( 1, 1 ) );
20930
21612
  }
20931
21613
 
21614
+ if (tile.isRightMost && tiledImage.wrapHorizontal) {
21615
+ sizeC.x += 0.75; // Otherwise Firefox and Safari show seams
21616
+ }
21617
+
21618
+ if (tile.isBottomMost && tiledImage.wrapVertical) {
21619
+ sizeC.y += 0.75; // Otherwise Firefox and Safari show seams
21620
+ }
21621
+
20932
21622
  tile.position = positionC;
20933
21623
  tile.size = sizeC;
20934
21624
  tile.squaredDistance = tileSquaredDistance;
@@ -20972,7 +21662,7 @@ function blendTile( tiledImage, tile, x, y, level, levelOpacity, currentTime ){
20972
21662
 
20973
21663
  tiledImage.lastDrawn.push( tile );
20974
21664
 
20975
- if ( opacity == 1 ) {
21665
+ if ( opacity === 1 ) {
20976
21666
  setCoverage( tiledImage.coverage, level, x, y, true );
20977
21667
  tiledImage._hasOpaqueTile = true;
20978
21668
  } else if ( deltaTime < blendTimeMillis ) {
@@ -21011,10 +21701,10 @@ function providesCoverage( coverage, level, x, y ) {
21011
21701
  if ( x === undefined || y === undefined ) {
21012
21702
  rows = coverage[ level ];
21013
21703
  for ( i in rows ) {
21014
- if ( rows.hasOwnProperty( i ) ) {
21704
+ if ( Object.prototype.hasOwnProperty.call( rows, i ) ) {
21015
21705
  cols = rows[ i ];
21016
21706
  for ( j in cols ) {
21017
- if ( cols.hasOwnProperty( j ) && !cols[ j ] ) {
21707
+ if ( Object.prototype.hasOwnProperty.call( cols, j ) && !cols[ j ] ) {
21018
21708
  return false;
21019
21709
  }
21020
21710
  }
@@ -21132,15 +21822,19 @@ function compareTiles( previousBest, tile ) {
21132
21822
  * @param {OpenSeadragon.Tile[]} lastDrawn - An unordered list of Tiles drawn last frame.
21133
21823
  */
21134
21824
  function drawTiles( tiledImage, lastDrawn ) {
21135
- if (tiledImage.opacity === 0 || lastDrawn.length === 0) {
21825
+ if (tiledImage.opacity === 0 || (lastDrawn.length === 0 && !tiledImage.placeholderFillStyle)) {
21136
21826
  return;
21137
21827
  }
21828
+
21138
21829
  var tile = lastDrawn[0];
21830
+ var useSketch;
21139
21831
 
21140
- var useSketch = tiledImage.opacity < 1 ||
21141
- (tiledImage.compositeOperation &&
21142
- tiledImage.compositeOperation !== 'source-over') ||
21143
- (!tiledImage._isBottomItem() && tile._hasTransparencyChannel());
21832
+ if (tile) {
21833
+ useSketch = tiledImage.opacity < 1 ||
21834
+ (tiledImage.compositeOperation &&
21835
+ tiledImage.compositeOperation !== 'source-over') ||
21836
+ (!tiledImage._isBottomItem() && tile._hasTransparencyChannel());
21837
+ }
21144
21838
 
21145
21839
  var sketchScale;
21146
21840
  var sketchTranslate;
@@ -21170,8 +21864,15 @@ function drawTiles( tiledImage, lastDrawn ) {
21170
21864
  // sketch canvas we are going to use for performance reasons.
21171
21865
  bounds = tiledImage.viewport.viewportToViewerElementRectangle(
21172
21866
  tiledImage.getClippedBounds(true))
21173
- .getIntegerBoundingBox()
21174
- .times($.pixelDensityRatio);
21867
+ .getIntegerBoundingBox();
21868
+
21869
+ if(tiledImage._drawer.viewer.viewport.getFlip()) {
21870
+ if (tiledImage.viewport.degrees !== 0 || tiledImage.getRotation(true) % 360 !== 0){
21871
+ bounds.x = tiledImage._drawer.viewer.container.clientWidth - (bounds.x + bounds.width);
21872
+ }
21873
+ }
21874
+
21875
+ bounds = bounds.times($.pixelDensityRatio);
21175
21876
  }
21176
21877
  tiledImage._drawer._clear(true, bounds);
21177
21878
  }
@@ -21193,6 +21894,12 @@ function drawTiles( tiledImage, lastDrawn ) {
21193
21894
  useSketch: useSketch
21194
21895
  });
21195
21896
  }
21897
+
21898
+ if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
21899
+ if(tiledImage._drawer.viewer.viewport.getFlip()) {
21900
+ tiledImage._drawer._flip();
21901
+ }
21902
+ }
21196
21903
  }
21197
21904
 
21198
21905
  var usedClip = false;
@@ -21213,6 +21920,28 @@ function drawTiles( tiledImage, lastDrawn ) {
21213
21920
  usedClip = true;
21214
21921
  }
21215
21922
 
21923
+ if (tiledImage._croppingPolygons) {
21924
+ tiledImage._drawer.saveContext(useSketch);
21925
+ try {
21926
+ var polygons = tiledImage._croppingPolygons.map(function (polygon) {
21927
+ return polygon.map(function (coord) {
21928
+ var point = tiledImage
21929
+ .imageToViewportCoordinates(coord.x, coord.y, true)
21930
+ .rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true));
21931
+ var clipPoint = tiledImage._drawer.viewportCoordToDrawerCoord(point);
21932
+ if (sketchScale) {
21933
+ clipPoint = clipPoint.times(sketchScale);
21934
+ }
21935
+ return clipPoint;
21936
+ });
21937
+ });
21938
+ tiledImage._drawer.clipWithPolygons(polygons, useSketch);
21939
+ } catch (e) {
21940
+ $.console.error(e);
21941
+ }
21942
+ usedClip = true;
21943
+ }
21944
+
21216
21945
  if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) {
21217
21946
  var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true));
21218
21947
  if (sketchScale) {
@@ -21303,6 +22032,15 @@ function drawTiles( tiledImage, lastDrawn ) {
21303
22032
  }
21304
22033
  }
21305
22034
  }
22035
+
22036
+ if (!sketchScale) {
22037
+ if (tiledImage.viewport.degrees === 0 && tiledImage.getRotation(true) % 360 === 0){
22038
+ if(tiledImage._drawer.viewer.viewport.getFlip()) {
22039
+ tiledImage._drawer._flip();
22040
+ }
22041
+ }
22042
+ }
22043
+
21306
22044
  drawDebugInfo( tiledImage, lastDrawn );
21307
22045
  }
21308
22046