openseadragon 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- 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