Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * Predefined zoom factors to be used when zooming in/out. These are in | |
| 7 * ascending order. | |
| 8 */ | |
| 9 var ZOOM_FACTORS = [0.25, 0.333, 0.5, 0.666, 0.75, 0.9, 1.0, | |
| 10 1.1, 1.25, 1.5, 1.75, 2.0, 2.5, 3.0, 4.0, 5.0]; | |
| 11 | |
| 12 /** | |
| 13 * Returns the area of the intersection of two rectangles. | |
| 14 * @param {dictionary} rect1 the first rect | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
Wrong type. Maybe {{x: number, y: number, width: n
raymes
2014/02/17 00:54:03
Done.
| |
| 15 * @param {dictionary} rect2 the second rect | |
| 16 * @return {number} the area of the intersection of the rects | |
| 17 */ | |
| 18 function getIntersectionArea(rect1, rect2) { | |
| 19 var xOverlap = Math.max(0, | |
| 20 Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - | |
| 21 Math.max(rect1.x, rect2.x)); | |
| 22 var yOverlap = Math.max(0, | |
| 23 Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - | |
| 24 Math.max(rect1.y, rect2.y)); | |
| 25 return xOverlap * yOverlap; | |
| 26 } | |
| 27 | |
| 28 /** | |
| 29 * Return the width of a scrollbar in pixels. | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
Useless comment
raymes
2014/02/17 00:54:03
Done.
| |
| 30 * @return {number} width of a scrollbar | |
| 31 */ | |
| 32 function getScrollbarWidth() { | |
| 33 var parentDiv = document.createElement('div'); | |
| 34 parentDiv.style.visibility = 'hidden'; | |
| 35 parentDiv.style.width = '500px'; | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
invalidates layout
raymes
2014/02/17 00:54:03
On 2014/02/13 23:47:41, arv wrote:
> invalidates l
| |
| 36 document.body.appendChild(parentDiv); | |
| 37 var parentDivWidth = parentDiv.offsetWidth; | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
forces layout
raymes
2014/02/17 00:54:03
On 2014/02/13 23:47:41, arv wrote:
> forces layout
| |
| 38 parentDiv.style.overflow = 'scroll'; | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
invalidates layout again.
Maybe restructure so th
raymes
2014/02/17 00:54:03
Could you explain a bit more how to improve the pe
| |
| 39 var childDiv = document.createElement('div'); | |
| 40 childDiv.style.width = '100%'; | |
| 41 parentDiv.appendChild(childDiv); | |
| 42 var childDivWidth = childDiv.offsetWidth; | |
| 43 parentDiv.parentNode.removeChild(parentDiv); | |
| 44 return parentDivWidth - childDivWidth; | |
| 45 } | |
| 46 | |
| 47 /** | |
| 48 * Create a new viewport. | |
| 49 * @param {object} window is the page window | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
invalid type
arv (Not doing code reviews)
2014/02/13 23:47:41
Badly formatted jsdoc
@param {Object} window The
raymes
2014/02/17 00:54:03
Done.
raymes
2014/02/17 00:54:03
Done.
| |
| 50 * @param {object} sizer is the element which represents the size of the | |
| 51 * document in the viewport | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
indent 4 spaces
raymes
2014/02/17 00:54:03
Done.
| |
| 52 * @param {function} fitToPageEnabledFunction returns true if fit-to-page is | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
Function or function(T1, T2) : T3
raymes
2014/02/17 00:54:03
Done.
| |
| 53 * enabled | |
| 54 * @param {function} viewportChangedCallback is run when the viewport changes | |
| 55 */ | |
| 56 Viewport = function(window, | |
| 57 sizer, | |
| 58 fitToPageEnabledFunction, | |
| 59 viewportChangedCallback) { | |
| 60 this.window_ = window; | |
| 61 this.sizer_ = sizer; | |
| 62 this.fitToPageEnabledFunction_ = fitToPageEnabledFunction; | |
| 63 this.viewportChangedCallback_ = viewportChangedCallback; | |
| 64 this.zoom_ = 1; | |
| 65 this.documentDimensions_ = {}; | |
| 66 this.pageDimensions_ = []; | |
| 67 this.scrollbarWidth_ = getScrollbarWidth(); | |
| 68 | |
| 69 window.addEventListener('scroll', this.updateViewport_.bind(this)); | |
| 70 }; | |
| 71 | |
| 72 /** | |
| 73 * @private | |
| 74 * Returns true if the document needs scrollbars at the given zoom level. | |
| 75 * @param {number} zoom compute whether scrollbars are needed at this zoom | |
| 76 * @return {dictionary} with 'x' and 'y' keys which map to bool values | |
| 77 * indicating if the horizontal and vertical scrollbars are needed respectively. | |
| 78 */ | |
| 79 Viewport.prototype.documentNeedsScrollbars_ = function(zoom) { | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
Viewport.prototype = {
documentNeedsScrollbars_:
raymes
2014/02/17 00:54:03
Done.
| |
| 80 return { | |
| 81 x: this.documentDimensions_.width * zoom > this.window_.innerWidth, | |
| 82 y: this.documentDimensions_.height * zoom > this.window_.innerHeight | |
| 83 }; | |
| 84 }; | |
| 85 | |
| 86 /** | |
| 87 * Returns true if the document needs scrollbars at the current zoom level. | |
| 88 * @return {dictionary} with 'x' and 'y' keys which map to bool values | |
| 89 * indicating if the horizontal and vertical scrollbars are needed respectively. | |
| 90 */ | |
| 91 Viewport.prototype.documentHasScrollbars = function() { | |
| 92 return this.documentNeedsScrollbars_(this.zoom_); | |
| 93 }; | |
| 94 | |
| 95 /** | |
| 96 * @private | |
| 97 * Helper function called when the zoomed document size changes. | |
| 98 */ | |
| 99 Viewport.prototype.contentSizeChanged_ = function() { | |
| 100 this.sizer_.style.width = this.documentDimensions_.width * this.zoom_ + 'px'; | |
| 101 this.sizer_.style.height = | |
| 102 this.documentDimensions_.height * this.zoom_ + 'px'; | |
| 103 }; | |
| 104 | |
| 105 /** | |
| 106 * Sets the zoom of the viewport. | |
| 107 * @param {number} newZoom the zoom level to zoom to | |
| 108 */ | |
| 109 Viewport.prototype.setZoom = function(newZoom) { | |
| 110 var oldZoom = this.zoom_; | |
| 111 this.zoom_ = newZoom; | |
| 112 // Record the scroll position (relative to the middle of the window). | |
| 113 var currentScrollPos = | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
var currentScrollPos = [
(this.window_.scrollX +
raymes
2014/02/17 00:54:03
Done.
| |
| 114 [(this.window_.scrollX + this.window_.innerWidth / 2.0) / oldZoom, | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
2.0 -> 2
raymes
2014/02/17 00:54:03
Done.
| |
| 115 (this.window_.scrollY + this.window_.innerHeight / 2.0) / oldZoom]; | |
| 116 this.contentSizeChanged_(); | |
| 117 // Scroll to the scaled scroll position. | |
| 118 this.window_.scrollTo( | |
| 119 (currentScrollPos[0] * newZoom) - this.window_.innerWidth / 2.0, | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
too many parens
raymes
2014/02/17 00:54:03
Done.
| |
| 120 (currentScrollPos[1] * newZoom) - this.window_.innerHeight / 2.0); | |
| 121 }; | |
| 122 | |
| 123 /** | |
| 124 * @private | |
| 125 * Called when the viewport should be updated. | |
| 126 */ | |
| 127 Viewport.prototype.updateViewport_ = function() { | |
| 128 // Shift the toolbar so that it doesn't move when the scrollbars display | |
| 129 var needsScrollbars = this.documentHasScrollbars(); | |
| 130 this.viewportChangedCallback_(this.zoom_, | |
| 131 this.window_.pageXOffset, | |
| 132 this.window_.pageYOffset, | |
| 133 this.scrollbarWidth_, | |
| 134 needsScrollbars); | |
| 135 }; | |
| 136 | |
| 137 /** | |
| 138 * @private | |
| 139 * Returns a rect representing the current viewport. | |
| 140 * @return {dictionary} a rect representing the current viewport. | |
| 141 */ | |
| 142 Viewport.prototype.getCurrentViewportRect_ = function() { | |
| 143 return { | |
| 144 'x': this.window_.pageXOffset / this.zoom_, | |
|
arv (Not doing code reviews)
2014/02/13 23:47:41
No need to quote the property names
raymes
2014/02/17 00:54:03
Done.
| |
| 145 'y': this.window_.pageYOffset / this.zoom_, | |
| 146 'width': this.window_.innerWidth / this.zoom_, | |
| 147 'height': this.window_.innerHeight / this.zoom_, | |
| 148 }; | |
| 149 }; | |
| 150 | |
| 151 /** | |
| 152 * Returns the page with the most pixels in the current viewport. | |
| 153 * @return {int} the index of the most visible page. | |
| 154 */ | |
| 155 Viewport.prototype.getMostVisiblePage = function() { | |
| 156 // TODO(raymes): Do a binary search here. | |
| 157 var mostVisiblePage = {'number': 0, 'area': 0}; | |
| 158 for (var i = 0; i < this.pageDimensions_.length; i++) { | |
| 159 var area = getIntersectionArea(this.pageDimensions_[i], | |
| 160 this.getCurrentViewportRect_()); | |
| 161 if (area > mostVisiblePage.area) { | |
| 162 mostVisiblePage.area = area; | |
| 163 mostVisiblePage.number = i; | |
| 164 } | |
| 165 } | |
| 166 return mostVisiblePage.number; | |
| 167 }; | |
| 168 | |
| 169 /** | |
| 170 * @private | |
| 171 * Compute the zoom level for fit-to-page or fit-to-width. |pageDimensions| is | |
| 172 * the dimensions for a given page and if |widthOnly| is true, it indicates that | |
| 173 * fit-to-page zoom should be computed rather than fit-to-page. | |
| 174 * @param {dictionary} pageDimensions the dimensions of a given page | |
| 175 * @param {boolean} widthOnly a bool indicating whether fit-to-page or | |
| 176 * fit-to-width should be computed. | |
| 177 * @return {number} the zoom to use | |
| 178 */ | |
| 179 Viewport.prototype.computeFittingZoom_ = function(pageDimensions, widthOnly) { | |
| 180 // First compute the zoom without scrollbars. | |
| 181 var zoomWidth = this.window_.innerWidth / pageDimensions.width; | |
| 182 var zoom; | |
| 183 if (widthOnly) { | |
| 184 zoom = zoomWidth; | |
| 185 } else { | |
| 186 var zoomHeight = this.window_.innerHeight / pageDimensions.height; | |
| 187 zoom = Math.min(zoomWidth, zoomHeight); | |
| 188 } | |
| 189 // Check if there needs to be any scrollbars. | |
| 190 var needsScrollbars = this.documentNeedsScrollbars_(zoom); | |
| 191 | |
| 192 // If the document fits, just return the zoom. | |
| 193 if (!needsScrollbars.x && !needsScrollbars.y) | |
| 194 return zoom; | |
| 195 | |
| 196 var zoomedDimensions = { | |
| 197 width: this.documentDimensions_.width * zoom, | |
| 198 height: this.documentDimensions_.height * zoom | |
| 199 }; | |
| 200 | |
| 201 // Check if adding a scrollbar will result in needing the other scrollbar. | |
| 202 var scrollbarWidth = this.scrollbarWidth_; | |
| 203 if (needsScrollbars.x && | |
| 204 zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) { | |
| 205 needsScrollbars.y = true; | |
| 206 } | |
| 207 if (needsScrollbars.y && | |
| 208 zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) { | |
| 209 needsScrollbars.x = true; | |
| 210 } | |
| 211 | |
| 212 // Compute available window space. | |
| 213 var windowWithScrollbars = { | |
| 214 width: this.window_.innerWidth, | |
| 215 height: this.window_.innerHeight | |
| 216 }; | |
| 217 if (needsScrollbars.x) | |
| 218 windowWithScrollbars.height -= scrollbarWidth; | |
| 219 if (needsScrollbars.y) | |
| 220 windowWithScrollbars.width -= scrollbarWidth; | |
| 221 | |
| 222 // Recompute the zoom. | |
| 223 zoomWidth = windowWithScrollbars.width / pageDimensions.width; | |
| 224 if (widthOnly) { | |
| 225 zoom = zoomWidth; | |
| 226 } else { | |
| 227 var zoomHeight = windowWithScrollbars.height / pageDimensions.height; | |
| 228 zoom = Math.min(zoomWidth, zoomHeight); | |
| 229 } | |
| 230 return zoom; | |
| 231 }; | |
| 232 | |
| 233 /** | |
| 234 * Zoom the viewport so that the page-width consumes the entire viewport. | |
| 235 */ | |
| 236 Viewport.prototype.fitToWidth = function() { | |
| 237 var page = this.getMostVisiblePage(); | |
| 238 this.setZoom(this.computeFittingZoom_(this.pageDimensions_[page], true)); | |
| 239 this.window_.scrollTo(this.pageDimensions_[page].x * this.zoom_, | |
| 240 this.window_.scrollY); | |
| 241 this.updateViewport_(); | |
| 242 }; | |
| 243 | |
| 244 /** | |
| 245 * Zoom the viewport so that a page consumes the entire viewport. Also scrolls | |
| 246 * to the top of the most visible page. | |
| 247 */ | |
| 248 Viewport.prototype.fitToPage = function() { | |
| 249 var page = this.getMostVisiblePage(); | |
| 250 this.setZoom(this.computeFittingZoom_(this.pageDimensions_[page], false)); | |
| 251 this.window_.scrollTo(this.pageDimensions_[page].x * this.zoom_, | |
| 252 this.pageDimensions_[page].y * this.zoom_); | |
| 253 this.updateViewport_(); | |
| 254 }; | |
| 255 | |
| 256 /** | |
| 257 * Zoom out to the next predefined zoom level. | |
| 258 */ | |
| 259 Viewport.prototype.zoomOut = function() { | |
| 260 var nextZoom = ZOOM_FACTORS[0]; | |
| 261 for (var i = 0; i < ZOOM_FACTORS.length; i++) { | |
| 262 if (ZOOM_FACTORS[i] < this.zoom_) | |
| 263 nextZoom = ZOOM_FACTORS[i]; | |
| 264 } | |
| 265 this.setZoom(nextZoom); | |
| 266 this.updateViewport_(); | |
| 267 }; | |
| 268 | |
| 269 /** | |
| 270 * Zoom in to the next predefined zoom level. | |
| 271 */ | |
| 272 Viewport.prototype.zoomIn = function() { | |
| 273 var nextZoom = ZOOM_FACTORS[ZOOM_FACTORS.length - 1]; | |
| 274 for (var i = ZOOM_FACTORS.length - 1; i >= 0; i--) { | |
| 275 if (ZOOM_FACTORS[i] > this.zoom_) | |
| 276 nextZoom = ZOOM_FACTORS[i]; | |
| 277 } | |
| 278 this.setZoom(nextZoom); | |
| 279 this.updateViewport_(); | |
| 280 }; | |
| 281 | |
| 282 /** | |
| 283 * Go to the given page index. | |
| 284 * @param {number} page the index of the page to go to | |
| 285 */ | |
| 286 Viewport.prototype.goToPage = function(page) { | |
| 287 if (page < 0) | |
| 288 page = 0; | |
| 289 var dimensions = this.pageDimensions_[page]; | |
| 290 this.window_.scrollTo(dimensions.x * this.zoom_, dimensions.y * this.zoom_); | |
| 291 }; | |
| 292 | |
| 293 /** | |
| 294 * Set the dimensions of the document. | |
| 295 * @param {dictionary} documentDimensions the dimensions of the document | |
| 296 */ | |
| 297 Viewport.prototype.setDocumentDimensions = function(documentDimensions) { | |
| 298 this.documentDimensions_ = documentDimensions; | |
| 299 this.pageDimensions_ = this.documentDimensions_.pageDimensions; | |
| 300 this.contentSizeChanged_(); | |
| 301 this.updateViewport_(); | |
| 302 }; | |
| OLD | NEW |