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