diff --git a/changelog.txt b/changelog.txt
index c9b2e1b4..df6ea660 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -39,7 +39,14 @@ OPENSEADRAGON CHANGELOG
* Rect and Point toString() functions are now consistent: rounding values to nearest hundredth
* Overlays appear in the DOM immediately on open or addOverlay (#507)
* imageLoaderLimit now works (#544)
+* Turning off scrollToZoom in gestureSettings now allows scroll events to propagate
+* You can now set a minZoomLevel that's greater than the home zoom level
+* Added union() to OpenSeadragon.Rect
+* Fixed an error in fitBounds if the new and old bounds were extremely close in size
* Added ajaxWithCredentials option (#543)
+* Added viewport-change event for after the viewport changes but before it's drawn
+* A spring's current value is now updated immediately on reset (#524)
+* Fixed an error in fitBounds that occurred sometimes with immediately = true
* Added support for HDPI (retina) displays (#583)
1.2.2: (in progress)
diff --git a/src/rectangle.js b/src/rectangle.js
index 6c8c9243..5d3495af 100644
--- a/src/rectangle.js
+++ b/src/rectangle.js
@@ -201,6 +201,21 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{
);
},
+ /**
+ * Returns the smallest rectangle that will contain this and the given rectangle.
+ * @param {OpenSeadragon.Rect} rect
+ * @return {OpenSeadragon.Rect} The new rectangle.
+ */
+ // ----------
+ union: function(rect) {
+ var left = Math.min(this.x, rect.x);
+ var top = Math.min(this.y, rect.y);
+ var right = Math.max(this.x + this.width, rect.x + rect.width);
+ var bottom = Math.max(this.y + this.height, rect.y + rect.height);
+
+ return new OpenSeadragon.Rect(left, top, right - left, bottom - top);
+ },
+
/**
* Rotates a rectangle around a point. Currently only 90, 180, and 270
* degrees are supported.
diff --git a/src/spring.js b/src/spring.js
index 4f92dfcb..3320ecc2 100644
--- a/src/spring.js
+++ b/src/spring.js
@@ -117,10 +117,8 @@ $.Spring.prototype = /** @lends OpenSeadragon.Spring.prototype */{
* @param {Number} target
*/
resetTo: function( target ) {
- this.target.value = target;
- this.target.time = this.current.time;
- this.start.value = this.target.value;
- this.start.time = this.target.time;
+ this.start.value = this.target.value = this.current.value = target;
+ this.start.time = this.target.time = this.current.time = $.now();
},
/**
diff --git a/src/tiledimage.js b/src/tiledimage.js
index c75bdc1c..ba587d07 100644
--- a/src/tiledimage.js
+++ b/src/tiledimage.js
@@ -409,8 +409,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
this._xSpring.resetTo(position.x);
this._ySpring.resetTo(position.y);
- this._xSpring.update();
- this._ySpring.update();
} else {
if (sameTarget) {
return;
@@ -454,7 +452,6 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
}
this._scaleSpring.resetTo(scale);
- this._scaleSpring.update();
this._updateForScale();
} else {
if (sameTarget) {
diff --git a/src/viewer.js b/src/viewer.js
index 06d81968..60e64e8a 100644
--- a/src/viewer.js
+++ b/src/viewer.js
@@ -2703,8 +2703,11 @@ function onCanvasScroll( event ) {
shift: event.shift,
originalEvent: event.originalEvent
});
- //cancels event
- return false;
+
+ if (gestureSettings && gestureSettings.scrollToZoom) {
+ //cancels event
+ return false;
+ }
}
function onContainerEnter( event ) {
@@ -2789,13 +2792,10 @@ function updateMulti( viewer ) {
function updateOnce( viewer ) {
- var containerSize,
- animated;
-
//viewer.profiler.beginUpdate();
if ( viewer.autoResize ) {
- containerSize = _getSafeElemSize( viewer.container );
+ var containerSize = _getSafeElemSize( viewer.container );
if ( !containerSize.equals( THIS[ viewer.hash ].prevContainerSize ) ) {
// maintain image position
var oldBounds = viewer.viewport.getBounds();
@@ -2806,8 +2806,22 @@ function updateOnce( viewer ) {
}
}
- animated = viewer.viewport.update();
- animated = viewer.world.update() || animated;
+ var viewportChange = viewer.viewport.update();
+ var animated = viewer.world.update() || viewportChange;
+
+ if (viewportChange) {
+ /**
+ * Raised when any spring animation update occurs (zoom, pan, etc.),
+ * before the viewer has drawn the new location.
+ *
+ * @event viewport-change
+ * @memberof OpenSeadragon.Viewer
+ * @type {object}
+ * @property {OpenSeadragon.Viewer} eventSource - A reference to the Viewer which raised this event.
+ * @property {?Object} userData - Arbitrary subscriber-defined object.
+ */
+ viewer.raiseEvent('viewport-change');
+ }
if( viewer.referenceStrip ){
animated = viewer.referenceStrip.update( viewer.viewport ) || animated;
@@ -2838,7 +2852,8 @@ function updateOnce( viewer ) {
if (animated) {
/**
- * Raised when any spring animation update occurs (zoom, pan, etc.).
+ * Raised when any spring animation update occurs (zoom, pan, etc.),
+ * after the viewer has drawn the new location.
*
* @event animation
* @memberof OpenSeadragon.Viewer
diff --git a/src/viewport.js b/src/viewport.js
index 44654f7e..b6b57e27 100644
--- a/src/viewport.js
+++ b/src/viewport.js
@@ -132,6 +132,10 @@ $.Viewport = function( options ) {
animationTime: this.animationTime
});
+ this._oldCenterX = this.centerSpringX.current.value;
+ this._oldCenterY = this.centerSpringY.current.value;
+ this._oldZoom = this.zoomSpring.current.value;
+
if (this.contentSize) {
this.resetContentSize( this.contentSize );
} else {
@@ -275,7 +279,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.minZoomLevel :
this.minZoomImageRatio * homeZoom;
- return Math.min( zoom, homeZoom );
+ return zoom;
},
/**
@@ -584,10 +588,17 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
}
newBounds = this._applyBoundaryConstraints( newBounds, immediately );
+ center = newBounds.getCenter();
}
- if ( newZoom == oldZoom || newBounds.width == oldBounds.width ) {
- return this.panTo( constraints ? newBounds.getCenter() : center, immediately );
+ if (immediately) {
+ this.panTo( center, true );
+ return this.zoomTo(newZoom, null, true);
+ }
+
+ if (Math.abs(newZoom - oldZoom) < 0.00000000001 ||
+ Math.abs(newBounds.width - oldBounds.width) < 0.00000000001) {
+ return this.panTo( center, immediately );
}
referencePoint = oldBounds.getTopLeft().times(
@@ -855,10 +866,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
* @function
*/
update: function() {
- var oldCenterX = this.centerSpringX.current.value,
- oldCenterY = this.centerSpringY.current.value,
- oldZoom = this.zoomSpring.current.value,
- oldZoomPixel,
+ var oldZoomPixel,
newZoomPixel,
deltaZoomPixels,
deltaZoomPoints;
@@ -869,7 +877,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.zoomSpring.update();
- if (this.zoomPoint && this.zoomSpring.current.value != oldZoom) {
+ if (this.zoomPoint && this.zoomSpring.current.value != this._oldZoom) {
newZoomPixel = this.pixelFromPoint( this.zoomPoint, true );
deltaZoomPixels = newZoomPixel.minus( oldZoomPixel );
deltaZoomPoints = this.deltaPointsFromPixels( deltaZoomPixels, true );
@@ -883,9 +891,15 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{
this.centerSpringX.update();
this.centerSpringY.update();
- return this.centerSpringX.current.value != oldCenterX ||
- this.centerSpringY.current.value != oldCenterY ||
- this.zoomSpring.current.value != oldZoom;
+ var changed = this.centerSpringX.current.value != this._oldCenterX ||
+ this.centerSpringY.current.value != this._oldCenterY ||
+ this.zoomSpring.current.value != this._oldZoom;
+
+ this._oldCenterX = this.centerSpringX.current.value;
+ this._oldCenterY = this.centerSpringY.current.value;
+ this._oldZoom = this.zoomSpring.current.value;
+
+ return changed;
},
diff --git a/test/demo/m2/README.md b/test/demo/m2/README.md
new file mode 100644
index 00000000..847f90c2
--- /dev/null
+++ b/test/demo/m2/README.md
@@ -0,0 +1,20 @@
+# M2 Demo
+
+This is an advanced demo/testbed, for proposed improvements to the new version of the Mirador project:
+
+https://github.com/IIIF/m2/
+
+You can see a previous version of Mirador here:
+
+http://showcase.iiif.io/viewer/mirador/
+
+## To Do
+
+* Choosing between multiple versions of a page
+* Detail images overlaid on the page
+* Cropped images
+
+### Maybe
+
+* Show/hide pages?
+* Lazyloading tilesources?
diff --git a/test/demo/m2/index.html b/test/demo/m2/index.html
new file mode 100644
index 00000000..e86c9a39
--- /dev/null
+++ b/test/demo/m2/index.html
@@ -0,0 +1,78 @@
+
+
+
+ Mirador POC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/demo/m2/js/.gitignore b/test/demo/m2/js/.gitignore
new file mode 100644
index 00000000..d13f3493
--- /dev/null
+++ b/test/demo/m2/js/.gitignore
@@ -0,0 +1 @@
+harvard-tilesources.js
diff --git a/test/demo/m2/js/main.js b/test/demo/m2/js/main.js
new file mode 100644
index 00000000..33af2b5b
--- /dev/null
+++ b/test/demo/m2/js/main.js
@@ -0,0 +1,670 @@
+/* globals $, App, d3 */
+
+(function() {
+ // ----------
+ window.App = {
+ // ----------
+ init: function() {
+ var self = this;
+
+ this.maxImages = 500;
+ this.mode = 'none';
+ this.pageBuffer = 0.05;
+ this.bigBuffer = 0.2;
+ this.page = 0;
+ this.modeNames = [
+ 'thumbs',
+ 'scroll',
+ 'book',
+ 'page'
+ ];
+
+ this.viewer = OpenSeadragon({
+ id: "contentDiv",
+ prefixUrl: "../../../build/openseadragon/images/",
+ autoResize: false,
+ showHomeControl: false,
+ tileSources: this.getTileSources()
+ });
+
+ this.viewer.addHandler('open', function() {
+ self.$el = $(self.viewer.element);
+ self.setMode({
+ mode: 'thumbs',
+ immediately: true
+ });
+ });
+
+ this.viewer.addHandler('canvas-drag', function() {
+ if (self.mode === 'scroll') {
+ var result = self.hitTest(self.viewer.viewport.getCenter());
+ if (result) {
+ self.page = result.index;
+ }
+ }
+ });
+
+ this.viewer.addHandler('viewport-change', function(event) {
+ self.applyConstraints();
+ });
+
+ $.each(this.modeNames, function(i, v) {
+ $('.' + v).click(function() {
+ self.setMode({
+ mode: v
+ });
+ });
+ });
+
+ $('.next').click(function() {
+ self.next();
+ });
+
+ $('.previous').click(function() {
+ self.previous();
+ });
+
+ $(window).keyup(function(event) {
+ if (self.mode === 'thumbs') {
+ return;
+ }
+
+ if (event.which === 39) { // Right arrow
+ self.next();
+ } else if (event.which === 37) { // Left arrow
+ self.previous();
+ }
+ });
+
+ this.$scrollInner = $('.scroll-inner');
+
+ this.$scrollCover = $('.scroll-cover')
+ .scroll(function(event) {
+ var info = self.getScrollInfo();
+ if (!info || self.ignoreScroll) {
+ return;
+ }
+
+ var pos = new OpenSeadragon.Point(info.thumbBounds.getCenter().x,
+ info.thumbBounds.y + (info.viewportHeight / 2) +
+ (info.viewportMax * info.scrollFactor));
+
+ self.viewer.viewport.panTo(pos, true);
+ })
+ .mousemove(function(event) {
+ var pixel = new OpenSeadragon.Point(event.clientX, event.clientY);
+ var result = self.hitTest(self.viewer.viewport.pointFromPixel(pixel));
+ self.updateHover(result ? result.index : -1);
+ })
+ .click(function(event) {
+ var pixel = new OpenSeadragon.Point(event.clientX, event.clientY);
+ var result = self.hitTest(self.viewer.viewport.pointFromPixel(pixel));
+ if (result) {
+ self.setMode({
+ mode: 'page',
+ page: result.index
+ });
+ }
+ });
+
+ var svgNode = this.viewer.svgOverlay();
+
+ this.highlight = d3.select(svgNode).append("rect")
+ .style('fill', 'none')
+ .style('stroke', '#08f')
+ .style('opacity', 0)
+ .style('stroke-width', 0.05)
+ .attr("pointer-events", "none");
+
+ this.hover = d3.select(svgNode).append("rect")
+ .style('fill', 'none')
+ .style('stroke', '#08f')
+ .style('opacity', 0)
+ .style('stroke-width', 0.05)
+ .attr("pointer-events", "none");
+
+ $(window).resize(function() {
+ var newSize = new OpenSeadragon.Point(self.$el.width(), self.$el.height());
+ self.viewer.viewport.resize(newSize, false);
+ self.setMode({
+ mode: self.mode,
+ immediately: true
+ });
+
+ self.viewer.forceRedraw();
+
+ self.viewer.svgOverlay('resize');
+ });
+
+ this.update();
+ },
+
+ // ----------
+ next: function() {
+ var page = this.page + (this.mode === 'book' ? 2 : 1);
+ if (this.mode === 'book' && page % 2 === 0 && page !== 0) {
+ page --;
+ }
+
+ this.goToPage({
+ page: page
+ });
+ },
+
+ // ----------
+ previous: function() {
+ var page = this.page - (this.mode === 'book' ? 2 : 1);
+ if (this.mode === 'book' && page % 2 === 0 && page !== 0) {
+ page --;
+ }
+
+ this.goToPage({
+ page: page
+ });
+ },
+
+ // ----------
+ hitTest: function(pos) {
+ var count = this.viewer.world.getItemCount();
+ var item, box;
+
+ for (var i = 0; i < count; i++) {
+ item = this.viewer.world.getItemAt(i);
+ box = item.getBounds();
+ if (pos.x > box.x && pos.y > box.y && pos.x < box.x + box.width &&
+ pos.y < box.y + box.height) {
+ return {
+ item: item,
+ index: i
+ };
+ }
+ }
+
+ return null;
+ },
+
+ // ----------
+ getScrollInfo: function() {
+ if (!this.thumbBounds) {
+ return null;
+ }
+
+ var output = {};
+
+ var viewerWidth = this.$el.width();
+ var viewerHeight = this.$el.height();
+ var scrollTop = this.$scrollCover.scrollTop();
+ output.scrollMax = this.$scrollInner.height() - this.$scrollCover.height();
+ output.scrollFactor = (output.scrollMax > 0 ? scrollTop / output.scrollMax : 0);
+
+ output.thumbBounds = this.thumbBounds;
+ output.viewportHeight = output.thumbBounds.width * (viewerHeight / viewerWidth);
+ output.viewportMax = Math.max(0, output.thumbBounds.height - output.viewportHeight);
+ return output;
+ },
+
+ // ----------
+ update: function() {
+ var self = this;
+
+ $('.nav').toggle(this.mode === 'scroll' || this.mode === 'book' || this.mode === 'page');
+ $('.previous').toggleClass('hidden', this.page <= 0);
+ $('.next').toggleClass('hidden', this.page >= this.viewer.world.getItemCount() - 1);
+
+ $.each(this.modeNames, function(i, v) {
+ $('.' + v).toggleClass('active', v === self.mode);
+ });
+ },
+
+ // ----------
+ applyConstraints: function() {
+ if (this.mode === 'thumbs') {
+ return;
+ }
+
+ if (this.panBounds) {
+ var center = this.viewer.viewport.getCenter(true);
+ var viewBounds = this.viewer.viewport.getBounds(true);
+ var bounds = this.panBounds.clone();
+ var left = bounds.x + (viewBounds.width / 2);
+ var top = bounds.y + (viewBounds.height / 2);
+ var right = (bounds.x + bounds.width) - (viewBounds.width / 2);
+ var bottom = (bounds.y + bounds.height) - (viewBounds.height / 2);
+
+ var x;
+ if (left <= right) {
+ x = Math.max(left, Math.min(right, center.x));
+ } else {
+ x = bounds.x + (bounds.width / 2);
+ }
+
+ var y;
+ if (top <= bottom) {
+ y = Math.max(top, Math.min(bottom, center.y));
+ } else {
+ y = bounds.y + (bounds.height / 2);
+ }
+
+ if (x !== center.x || y !== center.y) {
+ this.viewer.viewport.centerSpringX.current.value = x;
+ this.viewer.viewport.centerSpringY.current.value = y;
+ }
+ }
+ },
+
+ // ----------
+ setMode: function(config) {
+ var self = this;
+
+ this.mode = config.mode;
+
+ if (config.page !== undefined) {
+ this.page = config.page; // Need to do this before layout
+ }
+
+ this.ignoreScroll = true;
+ this.thumbBounds = null;
+
+ var layout = this.createLayout();
+
+ if (this.mode === 'thumbs') {
+ this.viewer.gestureSettingsMouse.scrollToZoom = false;
+ this.viewer.zoomPerClick = 1;
+ this.viewer.panHorizontal = false;
+ this.viewer.panVertical = false;
+ var viewerWidth = this.$el.width();
+ var width = layout.bounds.width + (this.bigBuffer * 2);
+ var height = layout.bounds.height + (this.bigBuffer * 2);
+ var newHeight = viewerWidth * (height / width);
+ this.$scrollCover.show();
+ this.$scrollInner
+ .css({
+ height: newHeight
+ });
+ } else {
+ this.viewer.gestureSettingsMouse.scrollToZoom = true;
+ this.viewer.zoomPerClick = 2;
+ this.viewer.panHorizontal = true;
+ this.viewer.panVertical = true;
+ this.$scrollCover.hide();
+ }
+
+ this.setLayout({
+ layout: layout,
+ immediately: config.immediately
+ });
+
+ if (this.mode === 'thumbs') {
+ // Set up thumbBounds
+ this.thumbBounds = this.viewer.world.getHomeBounds();
+ this.thumbBounds.x -= this.bigBuffer;
+ this.thumbBounds.y -= this.bigBuffer;
+ this.thumbBounds.width += (this.bigBuffer * 2);
+ this.thumbBounds.height += (this.bigBuffer * 2);
+
+ // Scroll to the appropriate location
+ var info = this.getScrollInfo();
+
+ var viewportBounds = this.thumbBounds.clone();
+ viewportBounds.y += info.viewportMax * info.scrollFactor;
+ viewportBounds.height = info.viewportHeight;
+
+ var itemBounds = this.viewer.world.getItemAt(this.page).getBounds();
+ var top = itemBounds.y - this.bigBuffer;
+ var bottom = top + itemBounds.height + (this.bigBuffer * 2);
+
+ var normalY;
+ if (top < viewportBounds.y) {
+ normalY = top - this.thumbBounds.y;
+ } else if (bottom > viewportBounds.y + viewportBounds.height) {
+ normalY = (bottom - info.viewportHeight) - this.thumbBounds.y;
+ }
+
+ if (normalY !== undefined) {
+ var viewportFactor = normalY / info.viewportMax;
+ this.$scrollCover.scrollTop(info.scrollMax * viewportFactor);
+ }
+ }
+
+ this.goHome({
+ immediately: config.immediately
+ });
+
+ this.viewer.viewport.minZoomLevel = this.viewer.viewport.getZoom();
+
+ this.update();
+ this.updateHighlight();
+ this.updateHover(-1);
+
+ clearTimeout(this.scrollTimeout);
+ this.scrollTimeout = setTimeout(function() {
+ self.ignoreScroll = false;
+ }, this.viewer.animationTime * 1000);
+ },
+
+ // ----------
+ updateHighlight: function() {
+ if (this.mode !== 'thumbs') {
+ this.highlight.style('opacity', 0);
+ return;
+ }
+
+ var item = this.viewer.world.getItemAt(this.page);
+ var box = item.getBounds();
+
+ this.highlight
+ .style('opacity', 1)
+ .attr("x", box.x)
+ .attr("width", box.width)
+ .attr("y", box.y)
+ .attr("height", box.height);
+ },
+
+ // ----------
+ updateHover: function(page) {
+ if (page === -1 || this.mode !== 'thumbs') {
+ this.hover.style('opacity', 0);
+ this.$scrollCover.css({
+ 'cursor': 'default'
+ });
+
+ return;
+ }
+
+ this.$scrollCover.css({
+ 'cursor': 'pointer'
+ });
+
+ var item = this.viewer.world.getItemAt(page);
+ var box = item.getBounds();
+
+ this.hover
+ .style('opacity', 0.3)
+ .attr("x", box.x)
+ .attr("width", box.width)
+ .attr("y", box.y)
+ .attr("height", box.height);
+ },
+
+ // ----------
+ goToPage: function(config) {
+ var self = this;
+
+ var itemCount = this.viewer.world.getItemCount();
+ this.page = Math.max(0, Math.min(itemCount - 1, config.page));
+
+ var viewerWidth = this.$el.width();
+ var viewerHeight = this.$el.height();
+ var bounds = this.viewer.world.getItemAt(this.page).getBounds();
+ var x = bounds.x;
+ var y = bounds.y;
+ var width = bounds.width;
+ var height = bounds.height;
+ var box;
+
+ if (this.mode === 'book') {
+ var item;
+ if (this.page % 2) { // First in a pair
+ item = this.viewer.world.getItemAt(this.page + 1);
+ if (item) {
+ width += item.getBounds().width;
+ }
+ } else {
+ item = this.viewer.world.getItemAt(this.page - 1);
+ if (item) {
+ box = item.getBounds();
+ x -= width;
+ width += box.width;
+ }
+ }
+ }
+
+ x -= this.pageBuffer;
+ y -= this.pageBuffer;
+ width += (this.pageBuffer * 2);
+ height += (this.pageBuffer * 2);
+
+ if (this.mode === 'scroll') {
+ if (this.page === 0) {
+ x = bounds.x - this.pageBuffer;
+ width = height * (viewerWidth / viewerHeight);
+ } else if (this.page === this.viewer.world.getItemCount() - 1) {
+ width = height * (viewerWidth / viewerHeight);
+ x = (bounds.x + bounds.width + this.pageBuffer) - width;
+ }
+ }
+
+ box = new OpenSeadragon.Rect(x, y, width, height);
+ this.viewer.viewport.fitBounds(box, config.immediately);
+
+ this.panBounds = null;
+
+ var setPanBounds = function() {
+ if (self.mode === 'page' || self.mode === 'book') {
+ self.panBounds = box;
+ } else if (self.mode === 'scroll') {
+ self.panBounds = self.viewer.world.getItemAt(0).getBounds()
+ .union(self.viewer.world.getItemAt(itemCount - 1).getBounds());
+
+ self.panBounds.x -= self.pageBuffer;
+ self.panBounds.y -= self.pageBuffer;
+ self.panBounds.width += (self.pageBuffer * 2);
+ self.panBounds.height += (self.pageBuffer * 2);
+ }
+ };
+
+ clearTimeout(this.panBoundsTimeout);
+ if (config.immediately) {
+ setPanBounds();
+ } else {
+ this.panBoundsTimeout = setTimeout(setPanBounds, this.viewer.animationTime * 1000);
+ }
+
+ this.viewer.viewport.minZoomLevel = this.viewer.viewport.getZoom();
+
+ this.update();
+ },
+
+ // ----------
+ createLayout: function() {
+ var viewerWidth = this.$el.width();
+ var viewerHeight = this.$el.height();
+ var layoutConfig = {};
+
+ if (this.mode === 'thumbs') {
+ layoutConfig.columns = Math.floor(viewerWidth / 150);
+ layoutConfig.buffer = this.bigBuffer;
+ layoutConfig.sameWidth = true;
+ } else if (this.mode === 'scroll') {
+ layoutConfig.buffer = this.pageBuffer;
+ } else if (this.mode === 'book' || this.mode === 'page') {
+ layoutConfig.book = (this.mode === 'book');
+ var height = 1 + (this.pageBuffer * 2);
+ // Note that using window here is approximate, but that's close enough.
+ // We can't use viewer, because it may be stretched for the thumbs view.
+ layoutConfig.buffer = (height * ($(window).width() / $(window).height())) / 2;
+ }
+
+ var layout = {
+ bounds: null,
+ specs: []
+ };
+
+ var count = this.viewer.world.getItemCount();
+ var x = 0;
+ var y = 0;
+ var offset = new OpenSeadragon.Point();
+ var rowHeight = 0;
+ var item, box;
+ for (var i = 0; i < count; i++) {
+ item = this.viewer.world.getItemAt(i);
+ box = item.getBounds();
+
+ if (i === this.page) {
+ offset = box.getTopLeft().minus(new OpenSeadragon.Point(x, y));
+ }
+
+ box.x = x;
+ box.y = y;
+ if (layoutConfig.sameWidth) {
+ box.height = box.height / box.width;
+ box.width = 1;
+ } else {
+ box.width = box.width / box.height;
+ box.height = 1;
+ }
+
+ rowHeight = Math.max(rowHeight, box.height);
+
+ layout.specs.push({
+ item: item,
+ bounds: box
+ });
+
+ if (layoutConfig.columns && i % layoutConfig.columns === layoutConfig.columns - 1) {
+ x = 0;
+ y += rowHeight + layoutConfig.buffer;
+ rowHeight = 0;
+ } else {
+ if (!layoutConfig.book || i % 2 === 0) {
+ x += layoutConfig.buffer;
+ }
+
+ x += box.width;
+ }
+ }
+
+ var pos, spec;
+ for (i = 0; i < count; i++) {
+ spec = layout.specs[i];
+ pos = spec.bounds.getTopLeft().plus(offset);
+ spec.bounds.x = pos.x;
+ spec.bounds.y = pos.y;
+
+ if (layout.bounds) {
+ layout.bounds = layout.bounds.union(spec.bounds);
+ } else {
+ layout.bounds = spec.bounds.clone();
+ }
+ }
+
+ return layout;
+ },
+
+ // ----------
+ setLayout: function(config) {
+ var spec;
+
+ for (var i = 0; i < config.layout.specs.length; i++) {
+ spec = config.layout.specs[i];
+ spec.item.setPosition(spec.bounds.getTopLeft(), config.immediately);
+ spec.item.setWidth(spec.bounds.width, config.immediately);
+ }
+ },
+
+ // ----------
+ goHome: function(config) {
+ var viewerWidth = this.$el.width();
+ var viewerHeight = this.$el.height();
+ var layoutConfig = {};
+
+ if (this.mode === 'thumbs') {
+ var info = this.getScrollInfo();
+ var box = this.thumbBounds.clone();
+ box.height = box.width * (viewerHeight / viewerWidth);
+ box.y += info.viewportMax * info.scrollFactor;
+ this.viewer.viewport.fitBounds(box, config.immediately);
+ } else {
+ this.goToPage({
+ page: this.page,
+ immediately: config.immediately
+ });
+ }
+ },
+
+ // ----------
+ getTileSources: function() {
+ if (this.tileSources) {
+ return $.map(this.tileSources.slice(0, this.maxImages), function(v, i) {
+ return new OpenSeadragon.IIIFTileSource(v);
+ });
+ }
+
+ var inputs = [
+ {
+ Image: {
+ xmlns: "http://schemas.microsoft.com/deepzoom/2008",
+ Url: "http://openseadragon.github.io/example-images/highsmith/highsmith_files/",
+ Format: "jpg",
+ Overlap: "2",
+ TileSize: "256",
+ Size: {
+ Width: "7026",
+ Height: "9221"
+ }
+ }
+ }, {
+ Image: {
+ xmlns: "http://schemas.microsoft.com/deepzoom/2008",
+ Url: "http://openseadragon.github.io/example-images/duomo/duomo_files/",
+ Format: "jpg",
+ Overlap: "2",
+ TileSize: "256",
+ Size: {
+ Width: "13920",
+ Height: "10200"
+ }
+ }
+ }, {
+ // Image: {
+ // xmlns: "http://schemas.microsoft.com/deepzoom/2008",
+ // Url: "../../data/tall_files/",
+ // Format: "jpg",
+ // Overlap: "1",
+ // TileSize: "254",
+ // Size: {
+ // Width: "500",
+ // Height: "2000"
+ // }
+ // }
+ // }, {
+ // Image: {
+ // xmlns: "http://schemas.microsoft.com/deepzoom/2008",
+ // Url: "../../data/wide_files/",
+ // Format: "jpg",
+ // Overlap: "1",
+ // TileSize: "254",
+ // Size: {
+ // Width: "2000",
+ // Height: "500"
+ // }
+ // }
+ // }, {
+ Image: {
+ xmlns: "http://schemas.microsoft.com/deepzoom/2008",
+ Url: "../../data/testpattern_files/",
+ Format: "jpg",
+ Overlap: "1",
+ TileSize: "254",
+ Size: {
+ Width: "1000",
+ Height: "1000"
+ }
+ }
+ }
+ ];
+
+ var outputs = [];
+ for (var i = 0; i < this.maxImages; i++) {
+ outputs.push(inputs[Math.floor(Math.random() * inputs.length)]);
+ }
+
+ return outputs;
+ }
+ };
+
+ // ----------
+ $(document).ready(function() {
+ App.init();
+ });
+})();
diff --git a/test/demo/m2/js/openseadragon-svg-overlay.js b/test/demo/m2/js/openseadragon-svg-overlay.js
new file mode 100644
index 00000000..688d4494
--- /dev/null
+++ b/test/demo/m2/js/openseadragon-svg-overlay.js
@@ -0,0 +1,73 @@
+(function() {
+
+ if (!window.OpenSeadragon) {
+ console.error('[openseadragon-svg-overlay] requires OpenSeadragon');
+ return;
+ }
+
+ var svgNS = 'http://www.w3.org/2000/svg';
+
+ var update = function(viewer) {
+ var info = viewer._svgOverlayInfo;
+
+ if (info.containerWidth !== viewer.container.clientWidth) {
+ info.containerWidth = viewer.container.clientWidth;
+ info.svg.setAttribute('width', info.containerWidth);
+ }
+
+ if (info.containerHeight !== viewer.container.clientHeight) {
+ info.containerHeight = viewer.container.clientHeight;
+ info.svg.setAttribute('height', info.containerHeight);
+ }
+
+ var p = viewer.viewport.pixelFromPoint(new OpenSeadragon.Point(0, 0), true);
+ var zoom = viewer.viewport.getZoom(true);
+ var scale = viewer.container.clientWidth * zoom;
+ info.node.setAttribute('transform',
+ 'translate(' + p.x + ',' + p.y + ') scale(' + scale + ')');
+ };
+
+ OpenSeadragon.Viewer.prototype.svgOverlay = function(command) {
+ var self = this;
+
+ if (command === undefined) {
+ if (this._svgOverlayInfo) {
+ console.error('[openseadragon-svg-overlay] already initialized on this viewer');
+ return;
+ }
+
+ var info = this._svgOverlayInfo = {
+ containerWidth: 0,
+ containerHeight: 0
+ };
+
+ info.svg = document.createElementNS(svgNS, 'svg');
+ info.svg.setAttribute('pointer-events', 'none');
+ info.svg.style.position = 'absolute';
+ info.svg.style.left = 0;
+ info.svg.style.top = 0;
+ info.svg.style.width = '100%';
+ info.svg.style.height = '100%';
+ this.container.insertBefore(info.svg, this.canvas.nextSibling);
+
+ info.node = document.createElementNS(svgNS, 'g');
+ info.svg.appendChild(info.node);
+
+ this.addHandler('animation', function() {
+ update(self);
+ });
+
+ this.addHandler('open', function() {
+ update(self);
+ });
+
+ update(this);
+ return info.node;
+ } else if (command === 'resize') {
+ update(this);
+ } else {
+ console.error('[openseadragon-svg-overlay] unknown command: ' + command);
+ }
+ };
+
+})();