blob: 358922c606b52cf76267f86ea2f471db4540852b [file] [log] [blame]
[email protected]3528d6302014-02-19 08:13:071// Copyright 2014 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/**
sammcd06823a962014-11-10 04:51:156 * Returns the height of the intersection of two rectangles.
[email protected]3528d6302014-02-19 08:13:077 * @param {Object} rect1 the first rect
8 * @param {Object} rect2 the second rect
sammcd06823a962014-11-10 04:51:159 * @return {number} the height of the intersection of the rects
[email protected]3528d6302014-02-19 08:13:0710 */
sammcd06823a962014-11-10 04:51:1511function getIntersectionHeight(rect1, rect2) {
dbeam70db0fb2017-06-19 17:09:2712 return Math.max(
13 0,
[email protected]3528d6302014-02-19 08:13:0714 Math.min(rect1.y + rect1.height, rect2.y + rect2.height) -
dbeam70db0fb2017-06-19 17:09:2715 Math.max(rect1.y, rect2.y));
[email protected]3528d6302014-02-19 08:13:0716}
17
18/**
mcnee6e1abbf2016-11-09 18:05:3119 * Makes sure that the scale level doesn't get out of the limits.
20 * @param {number} scale The new scale level.
21 * @return {number} The scale clamped within the limits.
22 */
23function clampScale(scale) {
24 return Math.min(5, Math.max(0.25, scale));
25}
26
27/**
28 * Computes vector between two points.
29 * @param {!Object} p1 The first point.
30 * @param {!Object} p2 The second point.
31 * @return {!Object} The vector.
32 */
33function vectorDelta(p1, p2) {
dbeam70db0fb2017-06-19 17:09:2734 return {x: p2.x - p1.x, y: p2.y - p1.y};
mcnee6e1abbf2016-11-09 18:05:3135}
36
37function frameToPluginCoordinate(coordinateInFrame) {
38 var container = $('plugin');
39 return {
40 x: coordinateInFrame.x - container.getBoundingClientRect().left,
41 y: coordinateInFrame.y - container.getBoundingClientRect().top
42 };
43}
44
45/**
[email protected]3528d6302014-02-19 08:13:0746 * Create a new viewport.
alexandrec9754b9e42015-01-19 03:41:1947 * @constructor
[email protected]3528d6302014-02-19 08:13:0748 * @param {Window} window the window
49 * @param {Object} sizer is the element which represents the size of the
50 * document in the viewport
[email protected]3528d6302014-02-19 08:13:0751 * @param {Function} viewportChangedCallback is run when the viewport changes
[email protected]499e9562014-06-26 05:45:2752 * @param {Function} beforeZoomCallback is run before a change in zoom
53 * @param {Function} afterZoomCallback is run after a change in zoom
[email protected]345e7c62014-05-02 09:52:5854 * @param {number} scrollbarWidth the width of scrollbars on the page
sammc43d4982f2015-04-22 07:46:5155 * @param {number} defaultZoom The default zoom level.
raymesbd82a942015-08-05 07:05:1856 * @param {number} topToolbarHeight The number of pixels that should initially
57 * be left blank above the document for the toolbar.
[email protected]3528d6302014-02-19 08:13:0758 */
dbeam70db0fb2017-06-19 17:09:2759function Viewport(
60 window, sizer, viewportChangedCallback, beforeZoomCallback,
61 afterZoomCallback, scrollbarWidth, defaultZoom, topToolbarHeight) {
[email protected]3528d6302014-02-19 08:13:0762 this.window_ = window;
63 this.sizer_ = sizer;
[email protected]3528d6302014-02-19 08:13:0764 this.viewportChangedCallback_ = viewportChangedCallback;
[email protected]499e9562014-06-26 05:45:2765 this.beforeZoomCallback_ = beforeZoomCallback;
66 this.afterZoomCallback_ = afterZoomCallback;
67 this.allowedToChangeZoom_ = false;
mcnee2999413a2016-12-06 20:29:2568 this.internalZoom_ = 1;
69 this.zoomManager_ = new InactiveZoomManager(this, 1);
[email protected]312112c72014-04-14 01:45:4370 this.documentDimensions_ = null;
[email protected]3528d6302014-02-19 08:13:0771 this.pageDimensions_ = [];
[email protected]345e7c62014-05-02 09:52:5872 this.scrollbarWidth_ = scrollbarWidth;
[email protected]312112c72014-04-14 01:45:4373 this.fittingType_ = Viewport.FittingType.NONE;
sammc43d4982f2015-04-22 07:46:5174 this.defaultZoom_ = defaultZoom;
raymesbd82a942015-08-05 07:05:1875 this.topToolbarHeight_ = topToolbarHeight;
mcnee6e1abbf2016-11-09 18:05:3176 this.prevScale_ = 1;
77 this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
78 this.pinchPanVector_ = null;
79 this.pinchCenter_ = null;
80 this.firstPinchCenterInFrame_ = null;
[email protected]3528d6302014-02-19 08:13:0781
82 window.addEventListener('scroll', this.updateViewport_.bind(this));
[email protected]312112c72014-04-14 01:45:4383 window.addEventListener('resize', this.resize_.bind(this));
[email protected]3528d6302014-02-19 08:13:0784}
85
[email protected]312112c72014-04-14 01:45:4386/**
87 * Enumeration of page fitting types.
88 * @enum {string}
89 */
90Viewport.FittingType = {
91 NONE: 'none',
92 FIT_TO_PAGE: 'fit-to-page',
93 FIT_TO_WIDTH: 'fit-to-width'
94};
95
96/**
mcnee6e1abbf2016-11-09 18:05:3197 * Enumeration of pinch states.
98 * This should match PinchPhase enum in pdf/out_of_process_instance.h
99 * @enum {number}
100 */
101Viewport.PinchPhase = {
102 PINCH_NONE: 0,
103 PINCH_START: 1,
104 PINCH_UPDATE_ZOOM_OUT: 2,
105 PINCH_UPDATE_ZOOM_IN: 3,
106 PINCH_END: 4
107};
108
109/**
[email protected]8dcaa262014-05-30 13:33:37110 * The increment to scroll a page by in pixels when up/down/left/right arrow
111 * keys are pressed. Usually we just let the browser handle scrolling on the
112 * window when these keys are pressed but in certain cases we need to simulate
113 * these events.
114 */
115Viewport.SCROLL_INCREMENT = 40;
116
117/**
[email protected]312112c72014-04-14 01:45:43118 * Predefined zoom factors to be used when zooming in/out. These are in
bsep24e7ec42016-08-25 02:11:20119 * ascending order. This should match the lists in
120 * components/ui/zoom/page_zoom_constants.h and
121 * chrome/browser/resources/settings/appearance_page/appearance_page.js
[email protected]312112c72014-04-14 01:45:43122 */
dbeam70db0fb2017-06-19 17:09:27123Viewport.ZOOM_FACTORS = [
124 0.25, 1 / 3, 0.5, 2 / 3, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2, 2.5, 3,
125 4, 5
126];
[email protected]312112c72014-04-14 01:45:43127
128/**
[email protected]4271e16b2014-08-22 12:18:59129 * The minimum and maximum range to be used to clip zoom factor.
130 */
131Viewport.ZOOM_FACTOR_RANGE = {
132 min: Viewport.ZOOM_FACTORS[0],
133 max: Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]
134};
135
136/**
[email protected]312112c72014-04-14 01:45:43137 * The width of the page shadow around pages in pixels.
138 */
dbeam70db0fb2017-06-19 17:09:27139Viewport.PAGE_SHADOW = {
140 top: 3,
141 bottom: 7,
142 left: 5,
143 right: 5
144};
[email protected]312112c72014-04-14 01:45:43145
[email protected]3528d6302014-02-19 08:13:07146Viewport.prototype = {
147 /**
raymese6e90c62015-08-10 06:21:40148 * Returns the zoomed and rounded document dimensions for the given zoom.
149 * Rounding is necessary when interacting with the renderer which tends to
150 * operate in integral values (for example for determining if scrollbars
151 * should be shown).
152 * @param {number} zoom The zoom to use to compute the scaled dimensions.
153 * @return {Object} A dictionary with scaled 'width'/'height' of the document.
154 * @private
155 */
156 getZoomedDocumentDimensions_: function(zoom) {
157 if (!this.documentDimensions_)
158 return null;
159 return {
160 width: Math.round(this.documentDimensions_.width * zoom),
161 height: Math.round(this.documentDimensions_.height * zoom)
162 };
163 },
164
165 /**
[email protected]3528d6302014-02-19 08:13:07166 * @private
167 * Returns true if the document needs scrollbars at the given zoom level.
168 * @param {number} zoom compute whether scrollbars are needed at this zoom
[email protected]312112c72014-04-14 01:45:43169 * @return {Object} with 'horizontal' and 'vertical' keys which map to bool
170 * values indicating if the horizontal and vertical scrollbars are needed
[email protected]3528d6302014-02-19 08:13:07171 * respectively.
172 */
173 documentNeedsScrollbars_: function(zoom) {
raymese6e90c62015-08-10 06:21:40174 var zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
175 if (!zoomedDimensions) {
dbeam70db0fb2017-06-19 17:09:27176 return {horizontal: false, vertical: false};
[email protected]499e9562014-06-26 05:45:27177 }
sammc63334fed2014-11-10 03:07:35178
179 // If scrollbars are required for one direction, expand the document in the
180 // other direction to take the width of the scrollbars into account when
181 // deciding whether the other direction needs scrollbars.
raymese6e90c62015-08-10 06:21:40182 if (zoomedDimensions.width > this.window_.innerWidth)
183 zoomedDimensions.height += this.scrollbarWidth_;
184 else if (zoomedDimensions.height > this.window_.innerHeight)
185 zoomedDimensions.width += this.scrollbarWidth_;
[email protected]3528d6302014-02-19 08:13:07186 return {
raymese6e90c62015-08-10 06:21:40187 horizontal: zoomedDimensions.width > this.window_.innerWidth,
raymes051fb2c2015-09-21 04:56:41188 vertical: zoomedDimensions.height + this.topToolbarHeight_ >
189 this.window_.innerHeight
[email protected]3528d6302014-02-19 08:13:07190 };
191 },
192
193 /**
194 * Returns true if the document needs scrollbars at the current zoom level.
195 * @return {Object} with 'x' and 'y' keys which map to bool values
196 * indicating if the horizontal and vertical scrollbars are needed
197 * respectively.
198 */
199 documentHasScrollbars: function() {
mcnee2999413a2016-12-06 20:29:25200 return this.documentNeedsScrollbars_(this.zoom);
[email protected]3528d6302014-02-19 08:13:07201 },
202
203 /**
204 * @private
205 * Helper function called when the zoomed document size changes.
206 */
207 contentSizeChanged_: function() {
mcnee2999413a2016-12-06 20:29:25208 var zoomedDimensions = this.getZoomedDocumentDimensions_(this.zoom);
raymese6e90c62015-08-10 06:21:40209 if (zoomedDimensions) {
210 this.sizer_.style.width = zoomedDimensions.width + 'px';
dbeam70db0fb2017-06-19 17:09:27211 this.sizer_.style.height =
212 zoomedDimensions.height + this.topToolbarHeight_ + 'px';
[email protected]345e7c62014-05-02 09:52:58213 }
[email protected]3528d6302014-02-19 08:13:07214 },
215
216 /**
[email protected]312112c72014-04-14 01:45:43217 * @private
218 * Called when the viewport should be updated.
[email protected]3528d6302014-02-19 08:13:07219 */
[email protected]312112c72014-04-14 01:45:43220 updateViewport_: function() {
221 this.viewportChangedCallback_();
222 },
223
224 /**
225 * @private
226 * Called when the viewport size changes.
227 */
228 resize_: function() {
229 if (this.fittingType_ == Viewport.FittingType.FIT_TO_PAGE)
raymes161514f2015-02-17 05:09:51230 this.fitToPageInternal_(false);
[email protected]312112c72014-04-14 01:45:43231 else if (this.fittingType_ == Viewport.FittingType.FIT_TO_WIDTH)
232 this.fitToWidth();
233 else
234 this.updateViewport_();
235 },
236
237 /**
238 * @type {Object} the scroll position of the viewport.
239 */
240 get position() {
241 return {
242 x: this.window_.pageXOffset,
raymesbd82a942015-08-05 07:05:18243 y: this.window_.pageYOffset - this.topToolbarHeight_
[email protected]312112c72014-04-14 01:45:43244 };
245 },
246
247 /**
248 * Scroll the viewport to the specified position.
249 * @type {Object} position the position to scroll to.
250 */
251 set position(position) {
raymesbd82a942015-08-05 07:05:18252 this.window_.scrollTo(position.x, position.y + this.topToolbarHeight_);
[email protected]312112c72014-04-14 01:45:43253 },
254
255 /**
256 * @type {Object} the size of the viewport excluding scrollbars.
257 */
258 get size() {
mcnee2999413a2016-12-06 20:29:25259 var needsScrollbars = this.documentNeedsScrollbars_(this.zoom);
[email protected]312112c72014-04-14 01:45:43260 var scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0;
261 var scrollbarHeight = needsScrollbars.horizontal ? this.scrollbarWidth_ : 0;
262 return {
263 width: this.window_.innerWidth - scrollbarWidth,
264 height: this.window_.innerHeight - scrollbarHeight
265 };
266 },
267
268 /**
269 * @type {number} the zoom level of the viewport.
270 */
271 get zoom() {
mcnee2999413a2016-12-06 20:29:25272 return this.zoomManager_.applyBrowserZoom(this.internalZoom_);
273 },
274
275 /**
276 * Set the zoom manager.
277 * @type {ZoomManager} manager the zoom manager to set.
278 */
279 set zoomManager(manager) {
280 this.zoomManager_ = manager;
[email protected]312112c72014-04-14 01:45:43281 },
282
283 /**
mcnee6e1abbf2016-11-09 18:05:31284 * @type {Viewport.PinchPhase} The phase of the current pinch gesture for
285 * the viewport.
286 */
287 get pinchPhase() {
288 return this.pinchPhase_;
289 },
290
291 /**
292 * @type {Object} The panning caused by the current pinch gesture (as
293 * the deltas of the x and y coordinates).
294 */
295 get pinchPanVector() {
296 return this.pinchPanVector_;
297 },
298
299 /**
300 * @type {Object} The coordinates of the center of the current pinch gesture.
301 */
302 get pinchCenter() {
303 return this.pinchCenter_;
304 },
305
306 /**
[email protected]312112c72014-04-14 01:45:43307 * @private
[email protected]499e9562014-06-26 05:45:27308 * Used to wrap a function that might perform zooming on the viewport. This is
309 * required so that we can notify the plugin that zooming is in progress
310 * so that while zooming is taking place it can stop reacting to scroll events
311 * from the viewport. This is to avoid flickering.
312 */
313 mightZoom_: function(f) {
314 this.beforeZoomCallback_();
315 this.allowedToChangeZoom_ = true;
316 f();
317 this.allowedToChangeZoom_ = false;
318 this.afterZoomCallback_();
319 },
320
321 /**
322 * @private
[email protected]312112c72014-04-14 01:45:43323 * Sets the zoom of the viewport.
324 * @param {number} newZoom the zoom level to zoom to.
325 */
[email protected]fbad5bb2014-07-18 07:20:36326 setZoomInternal_: function(newZoom) {
327 if (!this.allowedToChangeZoom_) {
328 throw 'Called Viewport.setZoomInternal_ without calling ' +
dbeam70db0fb2017-06-19 17:09:27329 'Viewport.mightZoom_.';
[email protected]fbad5bb2014-07-18 07:20:36330 }
sammc5c33b7e2014-11-07 04:03:09331 // Record the scroll position (relative to the top-left of the window).
raymesbd82a942015-08-05 07:05:18332 var currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25333 x: this.position.x / this.zoom,
334 y: this.position.y / this.zoom
raymesbd82a942015-08-05 07:05:18335 };
mcnee2999413a2016-12-06 20:29:25336 this.internalZoom_ = newZoom;
[email protected]3528d6302014-02-19 08:13:07337 this.contentSizeChanged_();
338 // Scroll to the scaled scroll position.
raymesbd82a942015-08-05 07:05:18339 this.position = {
mcnee2999413a2016-12-06 20:29:25340 x: currentScrollPos.x * this.zoom,
341 y: currentScrollPos.y * this.zoom
raymesbd82a942015-08-05 07:05:18342 };
[email protected]3528d6302014-02-19 08:13:07343 },
344
345 /**
mcnee6e1abbf2016-11-09 18:05:31346 * @private
347 * Sets the zoom of the viewport.
348 * Same as setZoomInternal_ but for pinch zoom we have some more operations.
349 * @param {number} scaleDelta The zoom delta.
350 * @param {!Object} center The pinch center in content coordinates.
351 */
352 setPinchZoomInternal_: function(scaleDelta, center) {
dbeam70db0fb2017-06-19 17:09:27353 assert(
354 this.allowedToChangeZoom_,
mcnee6e1abbf2016-11-09 18:05:31355 'Called Viewport.setPinchZoomInternal_ without calling ' +
dbeam70db0fb2017-06-19 17:09:27356 'Viewport.mightZoom_.');
mcnee2999413a2016-12-06 20:29:25357 this.internalZoom_ = clampScale(this.internalZoom_ * scaleDelta);
mcnee6e1abbf2016-11-09 18:05:31358
359 var newCenterInContent = this.frameToContent(center);
360 var delta = {
361 x: (newCenterInContent.x - this.oldCenterInContent.x),
362 y: (newCenterInContent.y - this.oldCenterInContent.y)
363 };
364
365 // Record the scroll position (relative to the pinch center).
366 var currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25367 x: this.position.x - delta.x * this.zoom,
368 y: this.position.y - delta.y * this.zoom
mcnee6e1abbf2016-11-09 18:05:31369 };
370
371 this.contentSizeChanged_();
372 // Scroll to the scaled scroll position.
dbeam70db0fb2017-06-19 17:09:27373 this.position = {x: currentScrollPos.x, y: currentScrollPos.y};
mcnee6e1abbf2016-11-09 18:05:31374 },
375
376 /**
377 * @private
378 * Converts a point from frame to content coordinates.
379 * @param {!Object} framePoint The frame coordinates.
380 * @return {!Object} The content coordinates.
381 */
382 frameToContent: function(framePoint) {
383 // TODO(mcnee) Add a helper Point class to avoid duplicating operations
384 // on plain {x,y} objects.
385 return {
mcnee2999413a2016-12-06 20:29:25386 x: (framePoint.x + this.position.x) / this.zoom,
387 y: (framePoint.y + this.position.y) / this.zoom
mcnee6e1abbf2016-11-09 18:05:31388 };
389 },
390
391 /**
[email protected]fbad5bb2014-07-18 07:20:36392 * Sets the zoom to the given zoom level.
393 * @param {number} newZoom the zoom level to zoom to.
[email protected]499e9562014-06-26 05:45:27394 */
[email protected]fbad5bb2014-07-18 07:20:36395 setZoom: function(newZoom) {
sammc5050705e2015-03-27 00:01:27396 this.fittingType_ = Viewport.FittingType.NONE;
dbeam70db0fb2017-06-19 17:09:27397 newZoom = Math.max(
398 Viewport.ZOOM_FACTOR_RANGE.min,
399 Math.min(newZoom, Viewport.ZOOM_FACTOR_RANGE.max));
dpapad9afc2802017-08-09 22:01:43400 this.mightZoom_(() => {
[email protected]fbad5bb2014-07-18 07:20:36401 this.setZoomInternal_(newZoom);
402 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43403 });
[email protected]499e9562014-06-26 05:45:27404 },
405
406 /**
mcnee2999413a2016-12-06 20:29:25407 * Gets notified of the browser zoom changing seperately from the
408 * internal zoom.
409 * @param {number} oldBrowserZoom the previous value of the browser zoom.
410 */
411 updateZoomFromBrowserChange: function(oldBrowserZoom) {
dpapad9afc2802017-08-09 22:01:43412 this.mightZoom_(() => {
mcnee2999413a2016-12-06 20:29:25413 // Record the scroll position (relative to the top-left of the window).
414 var oldZoom = oldBrowserZoom * this.internalZoom_;
415 var currentScrollPos = {
416 x: this.position.x / oldZoom,
417 y: this.position.y / oldZoom
418 };
419 this.contentSizeChanged_();
420 // Scroll to the scaled scroll position.
421 this.position = {
422 x: currentScrollPos.x * this.zoom,
423 y: currentScrollPos.y * this.zoom
424 };
425 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43426 });
mcnee2999413a2016-12-06 20:29:25427 },
428
429 /**
[email protected]312112c72014-04-14 01:45:43430 * @type {number} the width of scrollbars in the viewport in pixels.
[email protected]3528d6302014-02-19 08:13:07431 */
[email protected]312112c72014-04-14 01:45:43432 get scrollbarWidth() {
433 return this.scrollbarWidth_;
[email protected]3528d6302014-02-19 08:13:07434 },
435
436 /**
[email protected]312112c72014-04-14 01:45:43437 * @type {Viewport.FittingType} the fitting type the viewport is currently in.
[email protected]3528d6302014-02-19 08:13:07438 */
[email protected]312112c72014-04-14 01:45:43439 get fittingType() {
440 return this.fittingType_;
[email protected]3528d6302014-02-19 08:13:07441 },
442
443 /**
[email protected]56b7e3c2014-02-20 04:31:24444 * @private
dpapadd2d64772017-05-19 02:30:38445 * @param {number} y the y-coordinate to get the page at.
446 * @return {number} the index of a page overlapping the given y-coordinate.
[email protected]56b7e3c2014-02-20 04:31:24447 */
448 getPageAtY_: function(y) {
449 var min = 0;
450 var max = this.pageDimensions_.length - 1;
451 while (max >= min) {
452 var page = Math.floor(min + ((max - min) / 2));
[email protected]13df2a42014-02-27 03:50:41453 // There might be a gap between the pages, in which case use the bottom
454 // of the previous page as the top for finding the page.
455 var top = 0;
456 if (page > 0) {
457 top = this.pageDimensions_[page - 1].y +
458 this.pageDimensions_[page - 1].height;
459 }
dbeam70db0fb2017-06-19 17:09:27460 var bottom =
461 this.pageDimensions_[page].y + this.pageDimensions_[page].height;
[email protected]13df2a42014-02-27 03:50:41462
[email protected]56b7e3c2014-02-20 04:31:24463 if (top <= y && bottom > y)
464 return page;
465 else if (top > y)
466 max = page - 1;
467 else
468 min = page + 1;
469 }
470 return 0;
471 },
472
473 /**
sammcd06823a962014-11-10 04:51:15474 * Returns the page with the greatest proportion of its height in the current
475 * viewport.
dpapadd2d64772017-05-19 02:30:38476 * @return {number} the index of the most visible page.
[email protected]3528d6302014-02-19 08:13:07477 */
478 getMostVisiblePage: function() {
mcnee2999413a2016-12-06 20:29:25479 var firstVisiblePage = this.getPageAtY_(this.position.y / this.zoom);
sammcd06823a962014-11-10 04:51:15480 if (firstVisiblePage == this.pageDimensions_.length - 1)
481 return firstVisiblePage;
482
[email protected]345e7c62014-05-02 09:52:58483 var viewportRect = {
mcnee2999413a2016-12-06 20:29:25484 x: this.position.x / this.zoom,
485 y: this.position.y / this.zoom,
486 width: this.size.width / this.zoom,
487 height: this.size.height / this.zoom
[email protected]345e7c62014-05-02 09:52:58488 };
dbeam70db0fb2017-06-19 17:09:27489 var firstVisiblePageVisibility =
490 getIntersectionHeight(
491 this.pageDimensions_[firstVisiblePage], viewportRect) /
sammcd06823a962014-11-10 04:51:15492 this.pageDimensions_[firstVisiblePage].height;
dbeam70db0fb2017-06-19 17:09:27493 var nextPageVisibility =
494 getIntersectionHeight(
495 this.pageDimensions_[firstVisiblePage + 1], viewportRect) /
sammcd06823a962014-11-10 04:51:15496 this.pageDimensions_[firstVisiblePage + 1].height;
497 if (nextPageVisibility > firstVisiblePageVisibility)
498 return firstVisiblePage + 1;
499 return firstVisiblePage;
[email protected]3528d6302014-02-19 08:13:07500 },
501
502 /**
503 * @private
504 * Compute the zoom level for fit-to-page or fit-to-width. |pageDimensions| is
505 * the dimensions for a given page and if |widthOnly| is true, it indicates
506 * that fit-to-page zoom should be computed rather than fit-to-page.
507 * @param {Object} pageDimensions the dimensions of a given page
508 * @param {boolean} widthOnly a bool indicating whether fit-to-page or
509 * fit-to-width should be computed.
mcnee2999413a2016-12-06 20:29:25510 * @return {number} the internal zoom to set
[email protected]3528d6302014-02-19 08:13:07511 */
512 computeFittingZoom_: function(pageDimensions, widthOnly) {
513 // First compute the zoom without scrollbars.
514 var zoomWidth = this.window_.innerWidth / pageDimensions.width;
515 var zoom;
alexandrec9754b9e42015-01-19 03:41:19516 var zoomHeight;
[email protected]3528d6302014-02-19 08:13:07517 if (widthOnly) {
518 zoom = zoomWidth;
519 } else {
alexandrec9754b9e42015-01-19 03:41:19520 zoomHeight = this.window_.innerHeight / pageDimensions.height;
[email protected]3528d6302014-02-19 08:13:07521 zoom = Math.min(zoomWidth, zoomHeight);
522 }
523 // Check if there needs to be any scrollbars.
524 var needsScrollbars = this.documentNeedsScrollbars_(zoom);
525
526 // If the document fits, just return the zoom.
[email protected]312112c72014-04-14 01:45:43527 if (!needsScrollbars.horizontal && !needsScrollbars.vertical)
[email protected]3528d6302014-02-19 08:13:07528 return zoom;
529
raymese6e90c62015-08-10 06:21:40530 var zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
[email protected]3528d6302014-02-19 08:13:07531
532 // Check if adding a scrollbar will result in needing the other scrollbar.
533 var scrollbarWidth = this.scrollbarWidth_;
[email protected]312112c72014-04-14 01:45:43534 if (needsScrollbars.horizontal &&
[email protected]3528d6302014-02-19 08:13:07535 zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43536 needsScrollbars.vertical = true;
[email protected]3528d6302014-02-19 08:13:07537 }
[email protected]312112c72014-04-14 01:45:43538 if (needsScrollbars.vertical &&
[email protected]3528d6302014-02-19 08:13:07539 zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43540 needsScrollbars.horizontal = true;
[email protected]3528d6302014-02-19 08:13:07541 }
542
543 // Compute available window space.
544 var windowWithScrollbars = {
545 width: this.window_.innerWidth,
546 height: this.window_.innerHeight
547 };
[email protected]312112c72014-04-14 01:45:43548 if (needsScrollbars.horizontal)
[email protected]3528d6302014-02-19 08:13:07549 windowWithScrollbars.height -= scrollbarWidth;
[email protected]312112c72014-04-14 01:45:43550 if (needsScrollbars.vertical)
[email protected]3528d6302014-02-19 08:13:07551 windowWithScrollbars.width -= scrollbarWidth;
552
553 // Recompute the zoom.
554 zoomWidth = windowWithScrollbars.width / pageDimensions.width;
555 if (widthOnly) {
556 zoom = zoomWidth;
557 } else {
alexandrec9754b9e42015-01-19 03:41:19558 zoomHeight = windowWithScrollbars.height / pageDimensions.height;
[email protected]3528d6302014-02-19 08:13:07559 zoom = Math.min(zoomWidth, zoomHeight);
560 }
mcnee2999413a2016-12-06 20:29:25561 return this.zoomManager_.internalZoomComponent(zoom);
[email protected]3528d6302014-02-19 08:13:07562 },
563
564 /**
565 * Zoom the viewport so that the page-width consumes the entire viewport.
566 */
567 fitToWidth: function() {
dpapad9afc2802017-08-09 22:01:43568 this.mightZoom_(() => {
[email protected]499e9562014-06-26 05:45:27569 this.fittingType_ = Viewport.FittingType.FIT_TO_WIDTH;
570 if (!this.documentDimensions_)
571 return;
[email protected]499e9562014-06-26 05:45:27572 // When computing fit-to-width, the maximum width of a page in the
573 // document is used, which is equal to the size of the document width.
dbeam70db0fb2017-06-19 17:09:27574 this.setZoomInternal_(
575 this.computeFittingZoom_(this.documentDimensions_, true));
[email protected]499e9562014-06-26 05:45:27576 var page = this.getMostVisiblePage();
[email protected]499e9562014-06-26 05:45:27577 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43578 });
[email protected]3528d6302014-02-19 08:13:07579 },
580
581 /**
raymes161514f2015-02-17 05:09:51582 * @private
583 * Zoom the viewport so that a page consumes the entire viewport.
584 * @param {boolean} scrollToTopOfPage Set to true if the viewport should be
585 * scrolled to the top of the current page. Set to false if the viewport
586 * should remain at the current scroll position.
[email protected]3528d6302014-02-19 08:13:07587 */
raymes161514f2015-02-17 05:09:51588 fitToPageInternal_: function(scrollToTopOfPage) {
dpapad9afc2802017-08-09 22:01:43589 this.mightZoom_(() => {
[email protected]499e9562014-06-26 05:45:27590 this.fittingType_ = Viewport.FittingType.FIT_TO_PAGE;
591 if (!this.documentDimensions_)
592 return;
593 var page = this.getMostVisiblePage();
sammc07f53aa2014-11-06 06:57:46594 // Fit to the current page's height and the widest page's width.
595 var dimensions = {
596 width: this.documentDimensions_.width,
597 height: this.pageDimensions_[page].height,
598 };
599 this.setZoomInternal_(this.computeFittingZoom_(dimensions, false));
raymesbd82a942015-08-05 07:05:18600 if (scrollToTopOfPage) {
dbeam70db0fb2017-06-19 17:09:27601 this.position = {x: 0, y: this.pageDimensions_[page].y * this.zoom};
raymesbd82a942015-08-05 07:05:18602 }
[email protected]499e9562014-06-26 05:45:27603 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43604 });
[email protected]3528d6302014-02-19 08:13:07605 },
606
607 /**
raymes161514f2015-02-17 05:09:51608 * Zoom the viewport so that a page consumes the entire viewport. Also scrolls
609 * the viewport to the top of the current page.
610 */
611 fitToPage: function() {
612 this.fitToPageInternal_(true);
613 },
614
615 /**
[email protected]3528d6302014-02-19 08:13:07616 * Zoom out to the next predefined zoom level.
617 */
618 zoomOut: function() {
dpapad9afc2802017-08-09 22:01:43619 this.mightZoom_(() => {
[email protected]499e9562014-06-26 05:45:27620 this.fittingType_ = Viewport.FittingType.NONE;
621 var nextZoom = Viewport.ZOOM_FACTORS[0];
622 for (var i = 0; i < Viewport.ZOOM_FACTORS.length; i++) {
mcnee2999413a2016-12-06 20:29:25623 if (Viewport.ZOOM_FACTORS[i] < this.internalZoom_)
[email protected]499e9562014-06-26 05:45:27624 nextZoom = Viewport.ZOOM_FACTORS[i];
625 }
[email protected]fbad5bb2014-07-18 07:20:36626 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27627 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43628 });
[email protected]3528d6302014-02-19 08:13:07629 },
630
631 /**
632 * Zoom in to the next predefined zoom level.
633 */
634 zoomIn: function() {
dpapad9afc2802017-08-09 22:01:43635 this.mightZoom_(() => {
[email protected]499e9562014-06-26 05:45:27636 this.fittingType_ = Viewport.FittingType.NONE;
637 var nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1];
638 for (var i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) {
mcnee2999413a2016-12-06 20:29:25639 if (Viewport.ZOOM_FACTORS[i] > this.internalZoom_)
[email protected]499e9562014-06-26 05:45:27640 nextZoom = Viewport.ZOOM_FACTORS[i];
641 }
[email protected]fbad5bb2014-07-18 07:20:36642 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27643 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43644 });
[email protected]3528d6302014-02-19 08:13:07645 },
646
647 /**
mcnee6e1abbf2016-11-09 18:05:31648 * Pinch zoom event handler.
649 * @param {!Object} e The pinch event.
650 */
651 pinchZoom: function(e) {
dpapad9afc2802017-08-09 22:01:43652 this.mightZoom_(() => {
mcnee6e1abbf2016-11-09 18:05:31653 this.pinchPhase_ = e.direction == 'out' ?
dbeam70db0fb2017-06-19 17:09:27654 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_OUT :
655 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_IN;
mcnee6e1abbf2016-11-09 18:05:31656
657 var scaleDelta = e.startScaleRatio / this.prevScale_;
658 this.pinchPanVector_ =
659 vectorDelta(e.center, this.firstPinchCenterInFrame_);
660
dbeam70db0fb2017-06-19 17:09:27661 var needsScrollbars =
662 this.documentNeedsScrollbars_(this.zoomManager_.applyBrowserZoom(
mcnee2999413a2016-12-06 20:29:25663 clampScale(this.internalZoom_ * scaleDelta)));
mcnee6e1abbf2016-11-09 18:05:31664
665 this.pinchCenter_ = e.center;
666
667 // If there's no horizontal scrolling, keep the content centered so the
668 // user can't zoom in on the non-content area.
669 // TODO(mcnee) Investigate other ways of scaling when we don't have
670 // horizontal scrolling. We want to keep the document centered,
671 // but this causes a potentially awkward transition when we start
672 // using the gesture center.
673 if (!needsScrollbars.horizontal) {
674 this.pinchCenter_ = {
675 x: this.window_.innerWidth / 2,
676 y: this.window_.innerHeight / 2
677 };
678 } else if (this.keepContentCentered_) {
679 this.oldCenterInContent =
680 this.frameToContent(frameToPluginCoordinate(e.center));
681 this.keepContentCentered_ = false;
682 }
683
dbeam70db0fb2017-06-19 17:09:27684 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:31685 this.updateViewport_();
686 this.prevScale_ = e.startScaleRatio;
dpapad9afc2802017-08-09 22:01:43687 });
mcnee6e1abbf2016-11-09 18:05:31688 },
689
690 pinchZoomStart: function(e) {
691 this.pinchPhase_ = Viewport.PinchPhase.PINCH_START;
692 this.prevScale_ = 1;
693 this.oldCenterInContent =
694 this.frameToContent(frameToPluginCoordinate(e.center));
695
mcnee2999413a2016-12-06 20:29:25696 var needsScrollbars = this.documentNeedsScrollbars_(this.zoom);
mcnee6e1abbf2016-11-09 18:05:31697 this.keepContentCentered_ = !needsScrollbars.horizontal;
698 // We keep track of begining of the pinch.
699 // By doing so we will be able to compute the pan distance.
700 this.firstPinchCenterInFrame_ = e.center;
701 },
702
703 pinchZoomEnd: function(e) {
dpapad9afc2802017-08-09 22:01:43704 this.mightZoom_(() => {
mcnee6e1abbf2016-11-09 18:05:31705 this.pinchPhase_ = Viewport.PinchPhase.PINCH_END;
706 var scaleDelta = e.startScaleRatio / this.prevScale_;
707 this.pinchCenter_ = e.center;
708
dbeam70db0fb2017-06-19 17:09:27709 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:31710 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43711 });
mcnee6e1abbf2016-11-09 18:05:31712
713 this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
714 this.pinchPanVector_ = null;
715 this.pinchCenter_ = null;
716 this.firstPinchCenterInFrame_ = null;
717 },
718
719 /**
[email protected]3528d6302014-02-19 08:13:07720 * Go to the given page index.
[email protected]f90b9a42014-08-20 05:37:34721 * @param {number} page the index of the page to go to. zero-based.
[email protected]3528d6302014-02-19 08:13:07722 */
723 goToPage: function(page) {
dpapad9afc2802017-08-09 22:01:43724 this.mightZoom_(() => {
alexandrec9754b9e42015-01-19 03:41:19725 if (this.pageDimensions_.length === 0)
[email protected]499e9562014-06-26 05:45:27726 return;
727 if (page < 0)
728 page = 0;
729 if (page >= this.pageDimensions_.length)
730 page = this.pageDimensions_.length - 1;
731 var dimensions = this.pageDimensions_[page];
raymesbd82a942015-08-05 07:05:18732 var toolbarOffset = 0;
733 // Unless we're in fit to page mode, scroll above the page by
734 // |this.topToolbarHeight_| so that the toolbar isn't covering it
735 // initially.
736 if (this.fittingType_ != Viewport.FittingType.FIT_TO_PAGE)
737 toolbarOffset = this.topToolbarHeight_;
738 this.position = {
mcnee2999413a2016-12-06 20:29:25739 x: dimensions.x * this.zoom,
740 y: dimensions.y * this.zoom - toolbarOffset
raymesbd82a942015-08-05 07:05:18741 };
[email protected]499e9562014-06-26 05:45:27742 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43743 });
[email protected]3528d6302014-02-19 08:13:07744 },
745
746 /**
747 * Set the dimensions of the document.
748 * @param {Object} documentDimensions the dimensions of the document
749 */
750 setDocumentDimensions: function(documentDimensions) {
dpapad9afc2802017-08-09 22:01:43751 this.mightZoom_(() => {
[email protected]499e9562014-06-26 05:45:27752 var initialDimensions = !this.documentDimensions_;
753 this.documentDimensions_ = documentDimensions;
754 this.pageDimensions_ = this.documentDimensions_.pageDimensions;
755 if (initialDimensions) {
dbeam70db0fb2017-06-19 17:09:27756 this.setZoomInternal_(Math.min(
757 this.defaultZoom_,
758 this.computeFittingZoom_(this.documentDimensions_, true)));
759 this.position = {x: 0, y: -this.topToolbarHeight_};
[email protected]499e9562014-06-26 05:45:27760 }
761 this.contentSizeChanged_();
762 this.resize_();
dpapad9afc2802017-08-09 22:01:43763 });
[email protected]312112c72014-04-14 01:45:43764 },
765
766 /**
767 * Get the coordinates of the page contents (excluding the page shadow)
768 * relative to the screen.
769 * @param {number} page the index of the page to get the rect for.
770 * @return {Object} a rect representing the page in screen coordinates.
771 */
772 getPageScreenRect: function(page) {
[email protected]499e9562014-06-26 05:45:27773 if (!this.documentDimensions_) {
dbeam70db0fb2017-06-19 17:09:27774 return {x: 0, y: 0, width: 0, height: 0};
[email protected]499e9562014-06-26 05:45:27775 }
[email protected]312112c72014-04-14 01:45:43776 if (page >= this.pageDimensions_.length)
777 page = this.pageDimensions_.length - 1;
778
779 var pageDimensions = this.pageDimensions_[page];
780
781 // Compute the page dimensions minus the shadows.
782 var insetDimensions = {
783 x: pageDimensions.x + Viewport.PAGE_SHADOW.left,
784 y: pageDimensions.y + Viewport.PAGE_SHADOW.top,
785 width: pageDimensions.width - Viewport.PAGE_SHADOW.left -
786 Viewport.PAGE_SHADOW.right,
787 height: pageDimensions.height - Viewport.PAGE_SHADOW.top -
788 Viewport.PAGE_SHADOW.bottom
789 };
790
[email protected]345e7c62014-05-02 09:52:58791 // Compute the x-coordinate of the page within the document.
792 // TODO(raymes): This should really be set when the PDF plugin passes the
793 // page coordinates, but it isn't yet.
794 var x = (this.documentDimensions_.width - pageDimensions.width) / 2 +
795 Viewport.PAGE_SHADOW.left;
796 // Compute the space on the left of the document if the document fits
797 // completely in the screen.
dbeam70db0fb2017-06-19 17:09:27798 var spaceOnLeft =
799 (this.size.width - this.documentDimensions_.width * this.zoom) / 2;
[email protected]345e7c62014-05-02 09:52:58800 spaceOnLeft = Math.max(spaceOnLeft, 0);
[email protected]312112c72014-04-14 01:45:43801
802 return {
mcnee2999413a2016-12-06 20:29:25803 x: x * this.zoom + spaceOnLeft - this.window_.pageXOffset,
804 y: insetDimensions.y * this.zoom - this.window_.pageYOffset,
805 width: insetDimensions.width * this.zoom,
806 height: insetDimensions.height * this.zoom
[email protected]312112c72014-04-14 01:45:43807 };
[email protected]3528d6302014-02-19 08:13:07808 }
809};