Index: chrome/browser/resources/pdf/viewport.js |
diff --git a/chrome/browser/resources/pdf/viewport.js b/chrome/browser/resources/pdf/viewport.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..c4027befa6ef2eef70f6d00adfec5c5cbedef325 |
--- /dev/null |
+++ b/chrome/browser/resources/pdf/viewport.js |
@@ -0,0 +1,303 @@ |
+// Copyright 2013 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+/** |
+ * Predefined zoom factors to be used when zooming in/out. |
+ */ |
+ZOOM_FACTORS = [0.25, 0.333, 0.5, 0.666, 0.75, 0.9, 1.0, |
koz (OOO until 15th September)
2014/02/03 03:40:43
add a 'var' here?
raymes
2014/02/05 06:05:17
Done.
|
+ 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0, 5.0]; |
+ |
+/** |
+ * Returns the area of the intersection of two rectangles. |
+ * @param {dictionary} rect1 the first rect |
+ * @param {dictionary} rect2 the second rect |
+ * @return {number} the area of the intersection of the rects |
+ */ |
+function getIntersectionArea(rect1, rect2) { |
+ var xOverlap = Math.max(0, |
+ Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - |
+ Math.max(rect1.x, rect2.x)); |
+ var yOverlap = Math.max(0, |
+ Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - |
+ Math.max(rect1.y, rect2.y)); |
+ return xOverlap * yOverlap; |
+} |
+ |
+/** |
+ * Return the width of a scrollbar in pixels. |
+ * @return {number} width of a scrollbar |
+ */ |
+function getScrollbarWidth() { |
+ var parentDiv = document.createElement('div'); |
+ parentDiv.style.visibility = 'hidden'; |
+ parentDiv.style.width = '500px'; |
+ document.body.appendChild(parentDiv); |
+ var parentDivWidth = parentDiv.offsetWidth; |
+ parentDiv.style.overflow = 'scroll'; |
+ var childDiv = document.createElement('div'); |
+ childDiv.style.width = '100%'; |
+ parentDiv.appendChild(childDiv); |
+ var childDivWidth = childDiv.offsetWidth; |
+ parentDiv.parentNode.removeChild(parentDiv); |
+ return parentDivWidth - childDivWidth; |
+} |
+ |
+/** |
+ * Create a new viewport. |
+ * @param {object} window is the page window |
+ * @param {object} sizer is the element which represents the size of the |
+ * document in the viewport |
+ * @param {function} fitToPageEnabledFunction returns true if fit-to-page is |
+ * enabled |
+ * @param {function} viewportChangedCallback is run when the viewport changes |
+ */ |
+Viewport = function(window, |
+ sizer, |
+ fitToPageEnabledFunction, |
+ viewportChangedCallback) { |
+ this.window_ = window; |
+ this.sizer_ = sizer; |
+ this.fitToPageEnabledFunction_ = fitToPageEnabledFunction; |
+ this.viewportChangedCallback_ = viewportChangedCallback; |
+ this.zoom_ = 1; |
+ this.documentDimensions_ = {}; |
+ this.pageDimensions_ = []; |
+ this.scrollbarWidth_ = getScrollbarWidth(); |
+ |
+ var scrollCallback = function() { |
+ this.updateViewport_(); |
+ }.bind(this); |
+ window.addEventListener('scroll', scrollCallback); |
koz (OOO until 15th September)
2014/02/03 03:40:43
This could be written as
window.addEventListener(
raymes
2014/02/05 06:05:17
Done.
|
+}; |
+ |
+/** |
+ * @private |
+ * Returns true if the document needs scrollbars at the given zoom level. |
+ * @param {number} zoom compute whether scrollbars are needed at this zoom |
+ * @return {dictionary} with 'x' and 'y' keys which map to bool values |
+ * indicating if the horizontal and vertical scrollbars are needed respectively. |
+ */ |
+Viewport.prototype.documentNeedsScrollbars_ = function(zoom) { |
+ return { |
+ x: this.documentDimensions_.width * zoom > this.window_.innerWidth, |
+ y: this.documentDimensions_.height * zoom > this.window_.innerHeight |
+ }; |
+}; |
+ |
+/** |
+ * Returns true if the document needs scrollbars at the current zoom level. |
+ * @return {dictionary} with 'x' and 'y' keys which map to bool values |
+ * indicating if the horizontal and vertical scrollbars are needed respectively. |
+ */ |
+Viewport.prototype.documentHasScrollbars = function() { |
+ return this.documentNeedsScrollbars_(this.zoom_); |
+}; |
+ |
+/** |
+ * @private |
+ * Helper function called when the zoomed document size changes. |
+ */ |
+Viewport.prototype.contentSizeChanged_ = function() { |
+ this.sizer_.style.width = this.documentDimensions_.width * this.zoom_ + 'px'; |
+ this.sizer_.style.height = |
+ this.documentDimensions_.height * this.zoom_ + 'px'; |
+}; |
+ |
+/** |
+ * Sets the zoom of the viewport. |
+ * @param {number} newZoom the zoom level to zoom to |
+ */ |
+Viewport.prototype.setZoom = function(newZoom) { |
+ var oldZoom = this.zoom_; |
+ this.zoom_ = newZoom; |
+ // Record the scroll position (relative to the middle of the window). |
+ var currentScrollPos = |
+ [(this.window_.scrollX + this.window_.innerWidth / 2.0) / oldZoom, |
+ (this.window_.scrollY + this.window_.innerHeight / 2.0) / oldZoom]; |
+ this.contentSizeChanged_(); |
+ // Scroll to the scaled scroll position. |
+ this.window_.scrollTo( |
+ (currentScrollPos[0] * newZoom) - this.window_.innerWidth / 2.0, |
+ (currentScrollPos[1] * newZoom) - this.window_.innerHeight / 2.0); |
+}; |
+ |
+/** |
+ * @private |
+ * Called when the viewport should be updated. |
+ */ |
+Viewport.prototype.updateViewport_ = function() { |
+ // Shift the toolbar so that it doesn't move when the scrollbars display |
+ var needsScrollbars = this.documentHasScrollbars(); |
+ var scrollbarWidth = this.scrollbarWidth_; |
koz (OOO until 15th September)
2014/02/03 03:40:43
nit: this can be inlined
raymes
2014/02/05 06:05:17
Done.
|
+ this.viewportChangedCallback_(this.zoom_, |
+ this.window_.pageXOffset, |
+ this.window_.pageYOffset, |
+ scrollbarWidth, |
+ needsScrollbars); |
+}; |
+ |
+/** |
+ * @private |
+ * Returns a rect representing the current viewport. |
+ * @return {dictionary} a rect representing the current viewport. |
+ */ |
+Viewport.prototype.getCurrentViewportRect_ = function() { |
+ return { |
+ 'x': this.window_.pageXOffset / this.zoom_, |
+ 'y': this.window_.pageYOffset / this.zoom_, |
+ 'width': this.window_.innerWidth / this.zoom_, |
+ 'height': this.window_.innerHeight / this.zoom_, |
+ }; |
+}; |
+ |
+/** |
+ * Returns the most visible page on the screen. |
koz (OOO until 15th September)
2014/02/03 03:40:43
Maybe rephase as "Returns the page with the most p
raymes
2014/02/05 06:05:17
Done.
|
+ * @return {int} the index of the most visible page. |
+ */ |
+Viewport.prototype.getMostVisiblePage = function() { |
+ // TODO(raymes): Do a binary search here. |
+ var mostVisiblePage = {'number': 0, 'area': 0}; |
+ for (var i = 0; i < this.pageDimensions_.length; i++) { |
+ var area = getIntersectionArea(this.pageDimensions_[i], |
+ this.getCurrentViewportRect_()); |
+ if (area > mostVisiblePage.area) { |
+ mostVisiblePage.area = area; |
+ mostVisiblePage.number = i; |
+ } |
+ } |
+ return mostVisiblePage.number; |
+}; |
+ |
+/** |
+ * @private |
+ * Compute the zoom level for fit-to-page or fit-to-width. |pageDimensions| is |
+ * the dimensions for a given page and if |widthOnly| is true, it indicates that |
+ * fit-to-page zoom should be computed rather than fit-to-page. |
+ * @param {dictionary} pageDimensions the dimensions of a given page |
+ * @param {boolean} widthOnly a bool indicating whether fit-to-page or |
+ * fit-to-width should be computed. |
+ * @return {number} the zoom to use |
+ */ |
+Viewport.prototype.computeFittingZoom_ = function(pageDimensions, widthOnly) { |
+ // First compute the zoom without scrollbars. |
+ var zoomWidth = this.window_.innerWidth / pageDimensions.width; |
+ var zoom; |
+ if (widthOnly) { |
+ zoom = zoomWidth; |
+ } else { |
+ var zoomHeight = this.window_.innerHeight / pageDimensions.height; |
+ zoom = Math.min(zoomWidth, zoomHeight); |
+ } |
+ // Check if there needs to be any scrollbars. |
+ var needsScrollbars = this.documentNeedsScrollbars_(zoom); |
+ |
+ // If the document fits, just return the zoom. |
+ if (!needsScrollbars.x && !needsScrollbars.y) |
+ return zoom; |
+ |
+ var zoomedDimensions = { |
+ width: this.documentDimensions_.width * zoom, |
+ height: this.documentDimensions_.height * zoom |
+ }; |
+ |
+ // Check if adding a scrollbar will result in needing the other scrollbar. |
+ var scrollbarWidth = getScrollbarWidth(); |
+ if (needsScrollbars.x && |
+ zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) { |
+ needsScrollbars.y = true; |
+ } |
+ if (needsScrollbars.y && |
+ zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) { |
+ needsScrollbars.x = true; |
+ } |
+ |
+ // Compute available window space. |
+ var windowWithScrollbars = { |
+ width: this.window_.innerWidth, |
+ height: this.window_.innerHeight |
+ }; |
+ if (needsScrollbars.x) |
+ windowWithScrollbars.height -= scrollbarWidth; |
+ if (needsScrollbars.y) |
+ windowWithScrollbars.width -= scrollbarWidth; |
+ |
+ // Recompute the zoom. |
+ zoomWidth = windowWithScrollbars.width / pageDimensions.width; |
+ if (widthOnly) { |
+ zoom = zoomWidth; |
+ } else { |
+ var zoomHeight = windowWithScrollbars.height / pageDimensions.height; |
+ zoom = Math.min(zoomWidth, zoomHeight); |
+ } |
+ return zoom; |
+}; |
+ |
+/** |
+ * Zoom the viewport so that the page-width consumes the entire viewport. |
+ */ |
+Viewport.prototype.fitToWidth = function() { |
+ var page = this.getMostVisiblePage(); |
+ this.setZoom(this.computeFittingZoom_(this.pageDimensions_[page], true)); |
+ this.updateViewport_(); |
+}; |
+ |
+/** |
+ * Zoom the viewport so that a page consumes the entire viewport. Also scrolls |
+ * to the top of the most visible page. |
+ */ |
+Viewport.prototype.fitToPage = function() { |
+ var page = this.getMostVisiblePage(); |
+ this.setZoom(this.computeFittingZoom_(this.pageDimensions_[page], false)); |
+ this.window_.scrollTo(this.pageDimensions_[page].x * this.zoom_, |
+ this.pageDimensions_[page].y * this.zoom_); |
+ this.updateViewport_(); |
+}; |
+ |
+/** |
+ * Zoom in to the next predefined zoom level. |
+ */ |
+Viewport.prototype.zoomIn = function() { |
+ var nextZoom = ZOOM_FACTORS[0]; |
+ for (var i = 0; i < ZOOM_FACTORS.length; i++) { |
+ if (ZOOM_FACTORS[i] < this.zoom_) |
koz (OOO until 15th September)
2014/02/03 03:40:43
Won't this get the next smallest zoom factor, whic
raymes
2014/02/05 06:05:17
You're right, good catch! - the ids were backward
|
+ nextZoom = ZOOM_FACTORS[i]; |
+ } |
+ this.setZoom(nextZoom); |
+ this.updateViewport_(); |
+}; |
+ |
+/** |
+ * Zoom out to the next predefined zoom level. |
+ */ |
+Viewport.prototype.zoomOut = function() { |
+ var nextZoom = ZOOM_FACTORS[ZOOM_FACTORS.length - 1]; |
+ for (var i = ZOOM_FACTORS.length - 1; i >= 0; i--) { |
+ if (ZOOM_FACTORS[i] > this.zoom_) |
+ nextZoom = ZOOM_FACTORS[i]; |
+ } |
+ this.setZoom(nextZoom); |
+ this.updateViewport_(); |
+}; |
+ |
+/** |
+ * Go to the given page index. |
+ * @param {number} page the index of the page to go to |
+ */ |
+Viewport.prototype.goToPage = function(page) { |
+ if (page < 0) |
+ page = 0; |
+ var dimensions = this.pageDimensions_[page]; |
+ this.window_.scrollTo(dimensions.x * this.zoom_, dimensions.y * this.zoom_); |
+}; |
+ |
+/** |
+ * Set the dimensions of the document. |
+ * @param {dictionary} documentDimensions the dimensions of the document |
+ */ |
+Viewport.prototype.setDocumentDimensions = function(documentDimensions) { |
+ this.documentDimensions_ = documentDimensions; |
+ this.pageDimensions_ = this.documentDimensions_.pageDimensions; |
+ this.contentSizeChanged_(); |
+ this.updateViewport_(); |
+}; |