OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Returns the area of the intersection of two rectangles. | 6 * Returns the area of the intersection of two rectangles. |
7 * @param {Object} rect1 the first rect | 7 * @param {Object} rect1 the first rect |
8 * @param {Object} rect2 the second rect | 8 * @param {Object} rect2 the second rect |
9 * @return {number} the area of the intersection of the rects | 9 * @return {number} the area of the intersection of the rects |
10 */ | 10 */ |
11 function getIntersectionArea(rect1, rect2) { | 11 function getIntersectionArea(rect1, rect2) { |
12 var xOverlap = Math.max(0, | 12 var xOverlap = Math.max(0, |
13 Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - | 13 Math.min(rect1.x + rect1.width, rect2.x + rect2.width) - |
14 Math.max(rect1.x, rect2.x)); | 14 Math.max(rect1.x, rect2.x)); |
15 var yOverlap = Math.max(0, | 15 var yOverlap = Math.max(0, |
16 Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - | 16 Math.min(rect1.y + rect1.height, rect2.y + rect2.height) - |
17 Math.max(rect1.y, rect2.y)); | 17 Math.max(rect1.y, rect2.y)); |
18 return xOverlap * yOverlap; | 18 return xOverlap * yOverlap; |
19 } | 19 } |
20 | 20 |
21 /** | 21 /** |
22 * Create a new viewport. | 22 * Create a new viewport. |
23 * @param {Window} window the window | 23 * @param {Window} window the window |
24 * @param {Object} sizer is the element which represents the size of the | 24 * @param {Object} sizer is the element which represents the size of the |
25 * document in the viewport | 25 * document in the viewport |
26 * @param {Function} viewportChangedCallback is run when the viewport changes | 26 * @param {Function} viewportChangedCallback is run when the viewport changes |
| 27 * @param {Function} beforeZoomCallback is run before a change in zoom |
| 28 * @param {Function} afterZoomCallback is run after a change in zoom |
27 * @param {number} scrollbarWidth the width of scrollbars on the page | 29 * @param {number} scrollbarWidth the width of scrollbars on the page |
28 */ | 30 */ |
29 function Viewport(window, | 31 function Viewport(window, |
30 sizer, | 32 sizer, |
31 viewportChangedCallback, | 33 viewportChangedCallback, |
| 34 beforeZoomCallback, |
| 35 afterZoomCallback, |
32 scrollbarWidth) { | 36 scrollbarWidth) { |
33 this.window_ = window; | 37 this.window_ = window; |
34 this.sizer_ = sizer; | 38 this.sizer_ = sizer; |
35 this.viewportChangedCallback_ = viewportChangedCallback; | 39 this.viewportChangedCallback_ = viewportChangedCallback; |
| 40 this.beforeZoomCallback_ = beforeZoomCallback; |
| 41 this.afterZoomCallback_ = afterZoomCallback; |
| 42 this.allowedToChangeZoom_ = false; |
36 this.zoom_ = 1; | 43 this.zoom_ = 1; |
37 this.documentDimensions_ = null; | 44 this.documentDimensions_ = null; |
38 this.pageDimensions_ = []; | 45 this.pageDimensions_ = []; |
39 this.scrollbarWidth_ = scrollbarWidth; | 46 this.scrollbarWidth_ = scrollbarWidth; |
40 this.fittingType_ = Viewport.FittingType.NONE; | 47 this.fittingType_ = Viewport.FittingType.NONE; |
41 | 48 |
42 window.addEventListener('scroll', this.updateViewport_.bind(this)); | 49 window.addEventListener('scroll', this.updateViewport_.bind(this)); |
43 window.addEventListener('resize', this.resize_.bind(this)); | 50 window.addEventListener('resize', this.resize_.bind(this)); |
44 } | 51 } |
45 | 52 |
(...skipping 30 matching lines...) Expand all Loading... |
76 Viewport.prototype = { | 83 Viewport.prototype = { |
77 /** | 84 /** |
78 * @private | 85 * @private |
79 * Returns true if the document needs scrollbars at the given zoom level. | 86 * Returns true if the document needs scrollbars at the given zoom level. |
80 * @param {number} zoom compute whether scrollbars are needed at this zoom | 87 * @param {number} zoom compute whether scrollbars are needed at this zoom |
81 * @return {Object} with 'horizontal' and 'vertical' keys which map to bool | 88 * @return {Object} with 'horizontal' and 'vertical' keys which map to bool |
82 * values indicating if the horizontal and vertical scrollbars are needed | 89 * values indicating if the horizontal and vertical scrollbars are needed |
83 * respectively. | 90 * respectively. |
84 */ | 91 */ |
85 documentNeedsScrollbars_: function(zoom) { | 92 documentNeedsScrollbars_: function(zoom) { |
| 93 if (!this.documentDimensions_) { |
| 94 return { |
| 95 horizontal: false, |
| 96 vertical: false |
| 97 }; |
| 98 } |
86 var documentWidth = this.documentDimensions_.width * zoom; | 99 var documentWidth = this.documentDimensions_.width * zoom; |
87 var documentHeight = this.documentDimensions_.height * zoom; | 100 var documentHeight = this.documentDimensions_.height * zoom; |
88 return { | 101 return { |
89 horizontal: documentWidth > this.window_.innerWidth, | 102 horizontal: documentWidth > this.window_.innerWidth, |
90 vertical: documentHeight > this.window_.innerHeight | 103 vertical: documentHeight > this.window_.innerHeight |
91 }; | 104 }; |
92 }, | 105 }, |
93 | 106 |
94 /** | 107 /** |
95 * Returns true if the document needs scrollbars at the current zoom level. | 108 * Returns true if the document needs scrollbars at the current zoom level. |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
168 | 181 |
169 /** | 182 /** |
170 * @type {number} the zoom level of the viewport. | 183 * @type {number} the zoom level of the viewport. |
171 */ | 184 */ |
172 get zoom() { | 185 get zoom() { |
173 return this.zoom_; | 186 return this.zoom_; |
174 }, | 187 }, |
175 | 188 |
176 /** | 189 /** |
177 * @private | 190 * @private |
| 191 * Used to wrap a function that might perform zooming on the viewport. This is |
| 192 * required so that we can notify the plugin that zooming is in progress |
| 193 * so that while zooming is taking place it can stop reacting to scroll events |
| 194 * from the viewport. This is to avoid flickering. |
| 195 */ |
| 196 mightZoom_: function(f) { |
| 197 this.beforeZoomCallback_(); |
| 198 this.allowedToChangeZoom_ = true; |
| 199 f(); |
| 200 this.allowedToChangeZoom_ = false; |
| 201 this.afterZoomCallback_(); |
| 202 }, |
| 203 |
| 204 /** |
| 205 * @private |
178 * Sets the zoom of the viewport. | 206 * Sets the zoom of the viewport. |
179 * @param {number} newZoom the zoom level to zoom to. | 207 * @param {number} newZoom the zoom level to zoom to. |
180 */ | 208 */ |
181 setZoom_: function(newZoom) { | 209 setZoom_: function(newZoom) { |
| 210 if (!this.allowedToChangeZoom_) |
| 211 throw 'Called Viewport.setZoom_ without calling Viewport.mightZoom_.'; |
182 var oldZoom = this.zoom_; | 212 var oldZoom = this.zoom_; |
183 this.zoom_ = newZoom; | 213 this.zoom_ = newZoom; |
184 // Record the scroll position (relative to the middle of the window). | 214 // Record the scroll position (relative to the middle of the window). |
185 var currentScrollPos = [ | 215 var currentScrollPos = [ |
186 (this.window_.pageXOffset + this.window_.innerWidth / 2) / oldZoom, | 216 (this.window_.pageXOffset + this.window_.innerWidth / 2) / oldZoom, |
187 (this.window_.pageYOffset + this.window_.innerHeight / 2) / oldZoom | 217 (this.window_.pageYOffset + this.window_.innerHeight / 2) / oldZoom |
188 ]; | 218 ]; |
189 this.contentSizeChanged_(); | 219 this.contentSizeChanged_(); |
190 // Scroll to the scaled scroll position. | 220 // Scroll to the scaled scroll position. |
191 this.window_.scrollTo( | 221 this.window_.scrollTo( |
192 currentScrollPos[0] * newZoom - this.window_.innerWidth / 2, | 222 currentScrollPos[0] * newZoom - this.window_.innerWidth / 2, |
193 currentScrollPos[1] * newZoom - this.window_.innerHeight / 2); | 223 currentScrollPos[1] * newZoom - this.window_.innerHeight / 2); |
194 }, | 224 }, |
195 | 225 |
196 /** | 226 /** |
| 227 * @private |
| 228 * Sets the zoom for testing purposes. |
| 229 */ |
| 230 setZoomForTest_: function(newZoom) { |
| 231 this.mightZoom_(function() { |
| 232 this.setZoom_(newZoom); |
| 233 }.bind(this)); |
| 234 }, |
| 235 |
| 236 /** |
197 * @type {number} the width of scrollbars in the viewport in pixels. | 237 * @type {number} the width of scrollbars in the viewport in pixels. |
198 */ | 238 */ |
199 get scrollbarWidth() { | 239 get scrollbarWidth() { |
200 return this.scrollbarWidth_; | 240 return this.scrollbarWidth_; |
201 }, | 241 }, |
202 | 242 |
203 /** | 243 /** |
204 * @type {Viewport.FittingType} the fitting type the viewport is currently in. | 244 * @type {Viewport.FittingType} the fitting type the viewport is currently in. |
205 */ | 245 */ |
206 get fittingType() { | 246 get fittingType() { |
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
326 var zoomHeight = windowWithScrollbars.height / pageDimensions.height; | 366 var zoomHeight = windowWithScrollbars.height / pageDimensions.height; |
327 zoom = Math.min(zoomWidth, zoomHeight); | 367 zoom = Math.min(zoomWidth, zoomHeight); |
328 } | 368 } |
329 return zoom; | 369 return zoom; |
330 }, | 370 }, |
331 | 371 |
332 /** | 372 /** |
333 * Zoom the viewport so that the page-width consumes the entire viewport. | 373 * Zoom the viewport so that the page-width consumes the entire viewport. |
334 */ | 374 */ |
335 fitToWidth: function() { | 375 fitToWidth: function() { |
336 this.fittingType_ = Viewport.FittingType.FIT_TO_WIDTH; | 376 this.mightZoom_(function() { |
337 if (!this.documentDimensions_) | 377 this.fittingType_ = Viewport.FittingType.FIT_TO_WIDTH; |
338 return; | 378 if (!this.documentDimensions_) |
339 // Track the last y-position so we stay at the same position after zooming. | 379 return; |
340 var oldY = this.window_.pageYOffset / this.zoom_; | 380 // Track the last y-position to stay at the same position after zooming. |
341 // When computing fit-to-width, the maximum width of a page in the document | 381 var oldY = this.window_.pageYOffset / this.zoom_; |
342 // is used, which is equal to the size of the document width. | 382 // When computing fit-to-width, the maximum width of a page in the |
343 this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true)); | 383 // document is used, which is equal to the size of the document width. |
344 var page = this.getMostVisiblePage(); | 384 this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true)); |
345 this.window_.scrollTo(0, oldY * this.zoom_); | 385 var page = this.getMostVisiblePage(); |
346 this.updateViewport_(); | 386 this.window_.scrollTo(0, oldY * this.zoom_); |
| 387 this.updateViewport_(); |
| 388 }.bind(this)); |
347 }, | 389 }, |
348 | 390 |
349 /** | 391 /** |
350 * Zoom the viewport so that a page consumes the entire viewport. Also scrolls | 392 * Zoom the viewport so that a page consumes the entire viewport. Also scrolls |
351 * to the top of the most visible page. | 393 * to the top of the most visible page. |
352 */ | 394 */ |
353 fitToPage: function() { | 395 fitToPage: function() { |
354 this.fittingType_ = Viewport.FittingType.FIT_TO_PAGE; | 396 this.mightZoom_(function() { |
355 if (!this.documentDimensions_) | 397 this.fittingType_ = Viewport.FittingType.FIT_TO_PAGE; |
356 return; | 398 if (!this.documentDimensions_) |
357 var page = this.getMostVisiblePage(); | 399 return; |
358 this.setZoom_(this.computeFittingZoom_(this.pageDimensions_[page], false)); | 400 var page = this.getMostVisiblePage(); |
359 // Center the document in the page by scrolling by the amount of empty | 401 this.setZoom_(this.computeFittingZoom_( |
360 // space to the left of the document. | 402 this.pageDimensions_[page], false)); |
361 var xOffset = | 403 // Center the document in the page by scrolling by the amount of empty |
362 (this.documentDimensions_.width - this.pageDimensions_[page].width) * | 404 // space to the left of the document. |
363 this.zoom_ / 2; | 405 var xOffset = |
364 this.window_.scrollTo(xOffset, | 406 (this.documentDimensions_.width - this.pageDimensions_[page].width) * |
365 this.pageDimensions_[page].y * this.zoom_); | 407 this.zoom_ / 2; |
366 this.updateViewport_(); | 408 this.window_.scrollTo(xOffset, |
| 409 this.pageDimensions_[page].y * this.zoom_); |
| 410 this.updateViewport_(); |
| 411 }.bind(this)); |
367 }, | 412 }, |
368 | 413 |
369 /** | 414 /** |
370 * Zoom out to the next predefined zoom level. | 415 * Zoom out to the next predefined zoom level. |
371 */ | 416 */ |
372 zoomOut: function() { | 417 zoomOut: function() { |
373 this.fittingType_ = Viewport.FittingType.NONE; | 418 this.mightZoom_(function() { |
374 var nextZoom = Viewport.ZOOM_FACTORS[0]; | 419 this.fittingType_ = Viewport.FittingType.NONE; |
375 for (var i = 0; i < Viewport.ZOOM_FACTORS.length; i++) { | 420 var nextZoom = Viewport.ZOOM_FACTORS[0]; |
376 if (Viewport.ZOOM_FACTORS[i] < this.zoom_) | 421 for (var i = 0; i < Viewport.ZOOM_FACTORS.length; i++) { |
377 nextZoom = Viewport.ZOOM_FACTORS[i]; | 422 if (Viewport.ZOOM_FACTORS[i] < this.zoom_) |
378 } | 423 nextZoom = Viewport.ZOOM_FACTORS[i]; |
379 this.setZoom_(nextZoom); | 424 } |
380 this.updateViewport_(); | 425 this.setZoom_(nextZoom); |
| 426 this.updateViewport_(); |
| 427 }.bind(this)); |
381 }, | 428 }, |
382 | 429 |
383 /** | 430 /** |
384 * Zoom in to the next predefined zoom level. | 431 * Zoom in to the next predefined zoom level. |
385 */ | 432 */ |
386 zoomIn: function() { | 433 zoomIn: function() { |
387 this.fittingType_ = Viewport.FittingType.NONE; | 434 this.mightZoom_(function() { |
388 var nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]; | 435 this.fittingType_ = Viewport.FittingType.NONE; |
389 for (var i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) { | 436 var nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]; |
390 if (Viewport.ZOOM_FACTORS[i] > this.zoom_) | 437 for (var i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) { |
391 nextZoom = Viewport.ZOOM_FACTORS[i]; | 438 if (Viewport.ZOOM_FACTORS[i] > this.zoom_) |
392 } | 439 nextZoom = Viewport.ZOOM_FACTORS[i]; |
393 this.setZoom_(nextZoom); | 440 } |
394 this.updateViewport_(); | 441 this.setZoom_(nextZoom); |
| 442 this.updateViewport_(); |
| 443 }.bind(this)); |
395 }, | 444 }, |
396 | 445 |
397 /** | 446 /** |
398 * Go to the given page index. | 447 * Go to the given page index. |
399 * @param {number} page the index of the page to go to. | 448 * @param {number} page the index of the page to go to. |
400 */ | 449 */ |
401 goToPage: function(page) { | 450 goToPage: function(page) { |
402 if (this.pageDimensions_.length == 0) | 451 this.mightZoom_(function() { |
403 return; | 452 if (this.pageDimensions_.length == 0) |
404 if (page < 0) | 453 return; |
405 page = 0; | 454 if (page < 0) |
406 if (page >= this.pageDimensions_.length) | 455 page = 0; |
407 page = this.pageDimensions_.length - 1; | 456 if (page >= this.pageDimensions_.length) |
408 var dimensions = this.pageDimensions_[page]; | 457 page = this.pageDimensions_.length - 1; |
409 this.window_.scrollTo(dimensions.x * this.zoom_, dimensions.y * this.zoom_); | 458 var dimensions = this.pageDimensions_[page]; |
| 459 this.window_.scrollTo(dimensions.x * this.zoom_, |
| 460 dimensions.y * this.zoom_); |
| 461 this.updateViewport_(); |
| 462 }.bind(this)); |
410 }, | 463 }, |
411 | 464 |
412 /** | 465 /** |
413 * Set the dimensions of the document. | 466 * Set the dimensions of the document. |
414 * @param {Object} documentDimensions the dimensions of the document | 467 * @param {Object} documentDimensions the dimensions of the document |
415 */ | 468 */ |
416 setDocumentDimensions: function(documentDimensions) { | 469 setDocumentDimensions: function(documentDimensions) { |
417 var initialDimensions = !this.documentDimensions_; | 470 this.mightZoom_(function() { |
418 this.documentDimensions_ = documentDimensions; | 471 var initialDimensions = !this.documentDimensions_; |
419 this.pageDimensions_ = this.documentDimensions_.pageDimensions; | 472 this.documentDimensions_ = documentDimensions; |
420 if (initialDimensions) { | 473 this.pageDimensions_ = this.documentDimensions_.pageDimensions; |
421 this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true)); | 474 if (initialDimensions) { |
422 if (this.zoom_ > 1) | 475 this.setZoom_(this.computeFittingZoom_(this.documentDimensions_, true)); |
423 this.setZoom_(1); | 476 if (this.zoom_ > 1) |
424 this.window_.scrollTo(0, 0); | 477 this.setZoom_(1); |
425 } | 478 this.window_.scrollTo(0, 0); |
426 this.contentSizeChanged_(); | 479 } |
427 this.resize_(); | 480 this.contentSizeChanged_(); |
| 481 this.resize_(); |
| 482 }.bind(this)); |
428 }, | 483 }, |
429 | 484 |
430 /** | 485 /** |
431 * Get the coordinates of the page contents (excluding the page shadow) | 486 * Get the coordinates of the page contents (excluding the page shadow) |
432 * relative to the screen. | 487 * relative to the screen. |
433 * @param {number} page the index of the page to get the rect for. | 488 * @param {number} page the index of the page to get the rect for. |
434 * @return {Object} a rect representing the page in screen coordinates. | 489 * @return {Object} a rect representing the page in screen coordinates. |
435 */ | 490 */ |
436 getPageScreenRect: function(page) { | 491 getPageScreenRect: function(page) { |
| 492 if (!this.documentDimensions_) { |
| 493 return { |
| 494 x: 0, |
| 495 y: 0, |
| 496 width: 0, |
| 497 height: 0 |
| 498 }; |
| 499 } |
437 if (page >= this.pageDimensions_.length) | 500 if (page >= this.pageDimensions_.length) |
438 page = this.pageDimensions_.length - 1; | 501 page = this.pageDimensions_.length - 1; |
439 | 502 |
440 var pageDimensions = this.pageDimensions_[page]; | 503 var pageDimensions = this.pageDimensions_[page]; |
441 | 504 |
442 // Compute the page dimensions minus the shadows. | 505 // Compute the page dimensions minus the shadows. |
443 var insetDimensions = { | 506 var insetDimensions = { |
444 x: pageDimensions.x + Viewport.PAGE_SHADOW.left, | 507 x: pageDimensions.x + Viewport.PAGE_SHADOW.left, |
445 y: pageDimensions.y + Viewport.PAGE_SHADOW.top, | 508 y: pageDimensions.y + Viewport.PAGE_SHADOW.top, |
446 width: pageDimensions.width - Viewport.PAGE_SHADOW.left - | 509 width: pageDimensions.width - Viewport.PAGE_SHADOW.left - |
(...skipping 14 matching lines...) Expand all Loading... |
461 spaceOnLeft = Math.max(spaceOnLeft, 0); | 524 spaceOnLeft = Math.max(spaceOnLeft, 0); |
462 | 525 |
463 return { | 526 return { |
464 x: x * this.zoom_ + spaceOnLeft - this.window_.pageXOffset, | 527 x: x * this.zoom_ + spaceOnLeft - this.window_.pageXOffset, |
465 y: insetDimensions.y * this.zoom_ - this.window_.pageYOffset, | 528 y: insetDimensions.y * this.zoom_ - this.window_.pageYOffset, |
466 width: insetDimensions.width * this.zoom_, | 529 width: insetDimensions.width * this.zoom_, |
467 height: insetDimensions.height * this.zoom_ | 530 height: insetDimensions.height * this.zoom_ |
468 }; | 531 }; |
469 } | 532 } |
470 }; | 533 }; |
OLD | NEW |