blob: dce8d534ccdf6a877d7ead3091ad49188f8bfffc [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/**
Henrique Nakashima585c7af02018-03-27 04:55:216 * @typedef {{
7 * x: number,
8 * y: number
9 * }}
10 */
11let Point;
12
13/**
14 * @typedef {{
Douglas Stockwell1752f7762018-11-28 23:41:3615 * x: (number|undefined),
16 * y: (number|undefined),
Henrique Nakashima585c7af02018-03-27 04:55:2117 * }}
18 */
19let PartialPoint;
20
21/**
Douglas Stockwell1752f7762018-11-28 23:41:3622 * @typedef {{
23 * x: number,
24 * y: number,
25 * width: number,
26 * heigh: number,
27 * }}
28 */
29let Rect;
30
31/**
sammcd06823a962014-11-10 04:51:1532 * Returns the height of the intersection of two rectangles.
Henrique Nakashimad5de6d0d2018-04-13 18:02:4233 *
Douglas Stockwell1752f7762018-11-28 23:41:3634 * @param {Rect} rect1 the first rect
35 * @param {Rect} rect2 the second rect
sammcd06823a962014-11-10 04:51:1536 * @return {number} the height of the intersection of the rects
[email protected]3528d6302014-02-19 08:13:0737 */
sammcd06823a962014-11-10 04:51:1538function getIntersectionHeight(rect1, rect2) {
dbeam70db0fb2017-06-19 17:09:2739 return Math.max(
40 0,
[email protected]3528d6302014-02-19 08:13:0741 Math.min(rect1.y + rect1.height, rect2.y + rect2.height) -
dbeam70db0fb2017-06-19 17:09:2742 Math.max(rect1.y, rect2.y));
[email protected]3528d6302014-02-19 08:13:0743}
44
45/**
mcnee6e1abbf2016-11-09 18:05:3146 * Computes vector between two points.
Henrique Nakashimad5de6d0d2018-04-13 18:02:4247 *
mcnee6e1abbf2016-11-09 18:05:3148 * @param {!Object} p1 The first point.
49 * @param {!Object} p2 The second point.
50 * @return {!Object} The vector.
51 */
52function vectorDelta(p1, p2) {
dbeam70db0fb2017-06-19 17:09:2753 return {x: p2.x - p1.x, y: p2.y - p1.y};
mcnee6e1abbf2016-11-09 18:05:3154}
55
56function frameToPluginCoordinate(coordinateInFrame) {
dstockwellafba4e42018-12-02 22:58:0657 const container = $('plugin');
mcnee6e1abbf2016-11-09 18:05:3158 return {
59 x: coordinateInFrame.x - container.getBoundingClientRect().left,
60 y: coordinateInFrame.y - container.getBoundingClientRect().top
61 };
62}
63
Douglas Stockwell1752f7762018-11-28 23:41:3664// TODO: convert Viewport to ES6 class syntax
mcnee6e1abbf2016-11-09 18:05:3165/**
[email protected]3528d6302014-02-19 08:13:0766 * Create a new viewport.
Henrique Nakashimad5de6d0d2018-04-13 18:02:4267 *
[email protected]3528d6302014-02-19 08:13:0768 * @param {Window} window the window
69 * @param {Object} sizer is the element which represents the size of the
70 * document in the viewport
[email protected]3528d6302014-02-19 08:13:0771 * @param {Function} viewportChangedCallback is run when the viewport changes
[email protected]499e9562014-06-26 05:45:2772 * @param {Function} beforeZoomCallback is run before a change in zoom
73 * @param {Function} afterZoomCallback is run after a change in zoom
rbpottera82dacc2017-09-08 19:39:3974 * @param {Function} setUserInitiatedCallback is run to indicate whether a zoom
75 * event is user initiated.
[email protected]345e7c62014-05-02 09:52:5876 * @param {number} scrollbarWidth the width of scrollbars on the page
sammc43d4982f2015-04-22 07:46:5177 * @param {number} defaultZoom The default zoom level.
raymesbd82a942015-08-05 07:05:1878 * @param {number} topToolbarHeight The number of pixels that should initially
79 * be left blank above the document for the toolbar.
Henrique Nakashimad5de6d0d2018-04-13 18:02:4280 * @constructor
[email protected]3528d6302014-02-19 08:13:0781 */
dbeam70db0fb2017-06-19 17:09:2782function Viewport(
83 window, sizer, viewportChangedCallback, beforeZoomCallback,
rbpottera82dacc2017-09-08 19:39:3984 afterZoomCallback, setUserInitiatedCallback, scrollbarWidth, defaultZoom,
85 topToolbarHeight) {
[email protected]3528d6302014-02-19 08:13:0786 this.window_ = window;
87 this.sizer_ = sizer;
[email protected]3528d6302014-02-19 08:13:0788 this.viewportChangedCallback_ = viewportChangedCallback;
[email protected]499e9562014-06-26 05:45:2789 this.beforeZoomCallback_ = beforeZoomCallback;
90 this.afterZoomCallback_ = afterZoomCallback;
rbpottera82dacc2017-09-08 19:39:3991 this.setUserInitiatedCallback_ = setUserInitiatedCallback;
[email protected]499e9562014-06-26 05:45:2792 this.allowedToChangeZoom_ = false;
mcnee2999413a2016-12-06 20:29:2593 this.internalZoom_ = 1;
94 this.zoomManager_ = new InactiveZoomManager(this, 1);
[email protected]312112c72014-04-14 01:45:4395 this.documentDimensions_ = null;
[email protected]3528d6302014-02-19 08:13:0796 this.pageDimensions_ = [];
[email protected]345e7c62014-05-02 09:52:5897 this.scrollbarWidth_ = scrollbarWidth;
Henrique Nakashima50b18e02017-11-21 23:29:5798 this.fittingType_ = FittingType.NONE;
sammc43d4982f2015-04-22 07:46:5199 this.defaultZoom_ = defaultZoom;
raymesbd82a942015-08-05 07:05:18100 this.topToolbarHeight_ = topToolbarHeight;
mcnee6e1abbf2016-11-09 18:05:31101 this.prevScale_ = 1;
102 this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
103 this.pinchPanVector_ = null;
104 this.pinchCenter_ = null;
105 this.firstPinchCenterInFrame_ = null;
Douglas Stockwell1752f7762018-11-28 23:41:36106 this.rotations_ = 0;
[email protected]3528d6302014-02-19 08:13:07107
108 window.addEventListener('scroll', this.updateViewport_.bind(this));
rbpottera82dacc2017-09-08 19:39:39109 window.addEventListener('resize', this.resizeWrapper_.bind(this));
[email protected]3528d6302014-02-19 08:13:07110}
111
[email protected]312112c72014-04-14 01:45:43112/**
mcnee6e1abbf2016-11-09 18:05:31113 * Enumeration of pinch states.
114 * This should match PinchPhase enum in pdf/out_of_process_instance.h
115 * @enum {number}
116 */
117Viewport.PinchPhase = {
118 PINCH_NONE: 0,
119 PINCH_START: 1,
120 PINCH_UPDATE_ZOOM_OUT: 2,
121 PINCH_UPDATE_ZOOM_IN: 3,
122 PINCH_END: 4
123};
124
125/**
[email protected]8dcaa262014-05-30 13:33:37126 * The increment to scroll a page by in pixels when up/down/left/right arrow
127 * keys are pressed. Usually we just let the browser handle scrolling on the
128 * window when these keys are pressed but in certain cases we need to simulate
129 * these events.
130 */
131Viewport.SCROLL_INCREMENT = 40;
132
133/**
[email protected]312112c72014-04-14 01:45:43134 * Predefined zoom factors to be used when zooming in/out. These are in
bsep24e7ec42016-08-25 02:11:20135 * ascending order. This should match the lists in
136 * components/ui/zoom/page_zoom_constants.h and
137 * chrome/browser/resources/settings/appearance_page/appearance_page.js
[email protected]312112c72014-04-14 01:45:43138 */
dbeam70db0fb2017-06-19 17:09:27139Viewport.ZOOM_FACTORS = [
140 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,
141 4, 5
142];
[email protected]312112c72014-04-14 01:45:43143
144/**
[email protected]4271e16b2014-08-22 12:18:59145 * The minimum and maximum range to be used to clip zoom factor.
146 */
147Viewport.ZOOM_FACTOR_RANGE = {
148 min: Viewport.ZOOM_FACTORS[0],
149 max: Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1]
150};
151
152/**
Kevin McNee4e78edc2017-10-26 19:55:54153 * Clamps the zoom factor (or page scale factor) to be within the limits.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42154 *
Kevin McNee4e78edc2017-10-26 19:55:54155 * @param {number} factor The zoom/scale factor.
156 * @return {number} The factor clamped within the limits.
157 */
158Viewport.clampZoom = function(factor) {
159 return Math.max(
160 Viewport.ZOOM_FACTOR_RANGE.min,
161 Math.min(factor, Viewport.ZOOM_FACTOR_RANGE.max));
162};
163
164/**
[email protected]312112c72014-04-14 01:45:43165 * The width of the page shadow around pages in pixels.
166 */
dbeam70db0fb2017-06-19 17:09:27167Viewport.PAGE_SHADOW = {
168 top: 3,
169 bottom: 7,
170 left: 5,
171 right: 5
172};
[email protected]312112c72014-04-14 01:45:43173
[email protected]3528d6302014-02-19 08:13:07174Viewport.prototype = {
Douglas Stockwell1752f7762018-11-28 23:41:36175
176 /**
177 * @param {number} n the number of clockwise 90-degree rotations to
178 * increment by.
179 */
180 rotateClockwise: function(n) {
181 this.rotations_ = (this.rotations_ + n) % 4;
182 },
183
184 /**
185 * @return {number} the number of clockwise 90-degree rotations that have been
186 * applied.
187 */
188 getClockwiseRotations: function() {
189 return this.rotations_;
190 },
191
192 /**
193 * Converts a page position (e.g. the location of a bookmark) to a screen
194 * position.
195 *
196 * @param {number} page
197 * @param {Point} point The position on `page`.
198 * @return The screen position.
199 */
200 convertPageToScreen: function(page, point) {
201 const dimensions = this.getPageInsetDimensions(page);
202
203 // width & height are already rotated.
204 const height = dimensions.height;
205 const width = dimensions.width;
206
207 const matrix = new DOMMatrix();
208
209 const rotation = this.rotations_ * 90;
210 // Set origin for rotation.
211 if (rotation == 90) {
212 matrix.translateSelf(width, 0);
213 } else if (rotation == 180) {
214 matrix.translateSelf(width, height);
215 } else if (rotation == 270) {
216 matrix.translateSelf(0, height);
217 }
218 matrix.rotateSelf(0, 0, rotation);
219
220 // Invert Y position with respect to height as page coordinates are
221 // measured from the bottom left.
222 matrix.translateSelf(0, height);
223 matrix.scaleSelf(1, -1);
224
225 const pointsToPixels = 96 / 72;
226 const result = matrix.transformPoint({
227 x: point.x * pointsToPixels,
228 y: point.y * pointsToPixels,
229 });
230 return {
231 x: result.x + Viewport.PAGE_SHADOW.left,
232 y: result.y + Viewport.PAGE_SHADOW.top,
233 };
234 },
235
236
[email protected]3528d6302014-02-19 08:13:07237 /**
raymese6e90c62015-08-10 06:21:40238 * Returns the zoomed and rounded document dimensions for the given zoom.
239 * Rounding is necessary when interacting with the renderer which tends to
240 * operate in integral values (for example for determining if scrollbars
241 * should be shown).
Henrique Nakashimad5de6d0d2018-04-13 18:02:42242 *
raymese6e90c62015-08-10 06:21:40243 * @param {number} zoom The zoom to use to compute the scaled dimensions.
244 * @return {Object} A dictionary with scaled 'width'/'height' of the document.
245 * @private
246 */
247 getZoomedDocumentDimensions_: function(zoom) {
Dan Beamd1cca6e2019-01-03 02:46:27248 if (!this.documentDimensions_) {
raymese6e90c62015-08-10 06:21:40249 return null;
Dan Beamd1cca6e2019-01-03 02:46:27250 }
raymese6e90c62015-08-10 06:21:40251 return {
252 width: Math.round(this.documentDimensions_.width * zoom),
253 height: Math.round(this.documentDimensions_.height * zoom)
254 };
255 },
256
257 /**
dstockwell118c5362019-01-03 03:01:34258 * Returns the document dimensions.
259 *
260 * @return {Point} A dictionary with the 'width'/'height' of the document.
261 */
262 getDocumentDimensions: function() {
263 return {
264 width: this.documentDimensions_.width,
265 height: this.documentDimensions_.height
266 };
267 },
268
269 /**
[email protected]3528d6302014-02-19 08:13:07270 * Returns true if the document needs scrollbars at the given zoom level.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42271 *
[email protected]3528d6302014-02-19 08:13:07272 * @param {number} zoom compute whether scrollbars are needed at this zoom
[email protected]312112c72014-04-14 01:45:43273 * @return {Object} with 'horizontal' and 'vertical' keys which map to bool
274 * values indicating if the horizontal and vertical scrollbars are needed
[email protected]3528d6302014-02-19 08:13:07275 * respectively.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42276 * @private
[email protected]3528d6302014-02-19 08:13:07277 */
278 documentNeedsScrollbars_: function(zoom) {
dstockwellafba4e42018-12-02 22:58:06279 const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
raymese6e90c62015-08-10 06:21:40280 if (!zoomedDimensions) {
dbeam70db0fb2017-06-19 17:09:27281 return {horizontal: false, vertical: false};
[email protected]499e9562014-06-26 05:45:27282 }
sammc63334fed2014-11-10 03:07:35283
284 // If scrollbars are required for one direction, expand the document in the
285 // other direction to take the width of the scrollbars into account when
286 // deciding whether the other direction needs scrollbars.
Dan Beamd1cca6e2019-01-03 02:46:27287 if (zoomedDimensions.width > this.window_.innerWidth) {
raymese6e90c62015-08-10 06:21:40288 zoomedDimensions.height += this.scrollbarWidth_;
Dan Beamd1cca6e2019-01-03 02:46:27289 } else if (zoomedDimensions.height > this.window_.innerHeight) {
raymese6e90c62015-08-10 06:21:40290 zoomedDimensions.width += this.scrollbarWidth_;
Dan Beamd1cca6e2019-01-03 02:46:27291 }
[email protected]3528d6302014-02-19 08:13:07292 return {
raymese6e90c62015-08-10 06:21:40293 horizontal: zoomedDimensions.width > this.window_.innerWidth,
raymes051fb2c2015-09-21 04:56:41294 vertical: zoomedDimensions.height + this.topToolbarHeight_ >
295 this.window_.innerHeight
[email protected]3528d6302014-02-19 08:13:07296 };
297 },
298
299 /**
300 * Returns true if the document needs scrollbars at the current zoom level.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42301 *
[email protected]3528d6302014-02-19 08:13:07302 * @return {Object} with 'x' and 'y' keys which map to bool values
303 * indicating if the horizontal and vertical scrollbars are needed
304 * respectively.
305 */
306 documentHasScrollbars: function() {
mcnee2999413a2016-12-06 20:29:25307 return this.documentNeedsScrollbars_(this.zoom);
[email protected]3528d6302014-02-19 08:13:07308 },
309
310 /**
[email protected]3528d6302014-02-19 08:13:07311 * Helper function called when the zoomed document size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42312 *
313 * @private
[email protected]3528d6302014-02-19 08:13:07314 */
315 contentSizeChanged_: function() {
dstockwellafba4e42018-12-02 22:58:06316 const zoomedDimensions = this.getZoomedDocumentDimensions_(this.zoom);
raymese6e90c62015-08-10 06:21:40317 if (zoomedDimensions) {
318 this.sizer_.style.width = zoomedDimensions.width + 'px';
dbeam70db0fb2017-06-19 17:09:27319 this.sizer_.style.height =
320 zoomedDimensions.height + this.topToolbarHeight_ + 'px';
[email protected]345e7c62014-05-02 09:52:58321 }
[email protected]3528d6302014-02-19 08:13:07322 },
323
324 /**
[email protected]312112c72014-04-14 01:45:43325 * Called when the viewport should be updated.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42326 *
327 * @private
[email protected]3528d6302014-02-19 08:13:07328 */
[email protected]312112c72014-04-14 01:45:43329 updateViewport_: function() {
330 this.viewportChangedCallback_();
331 },
332
333 /**
rbpottera82dacc2017-09-08 19:39:39334 * Called when the browser window size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42335 *
336 * @private
rbpottera82dacc2017-09-08 19:39:39337 */
338 resizeWrapper_: function() {
339 this.setUserInitiatedCallback_(false);
340 this.resize_();
341 this.setUserInitiatedCallback_(true);
342 },
343
344 /**
[email protected]312112c72014-04-14 01:45:43345 * Called when the viewport size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42346 *
347 * @private
[email protected]312112c72014-04-14 01:45:43348 */
349 resize_: function() {
Dan Beamd1cca6e2019-01-03 02:46:27350 if (this.fittingType_ == FittingType.FIT_TO_PAGE) {
raymes161514f2015-02-17 05:09:51351 this.fitToPageInternal_(false);
Dan Beamd1cca6e2019-01-03 02:46:27352 } else if (this.fittingType_ == FittingType.FIT_TO_WIDTH) {
[email protected]312112c72014-04-14 01:45:43353 this.fitToWidth();
Dan Beamd1cca6e2019-01-03 02:46:27354 } else if (this.fittingType_ == FittingType.FIT_TO_HEIGHT) {
Henrique Nakashima50b18e02017-11-21 23:29:57355 this.fitToHeightInternal_(false);
Dan Beamd1cca6e2019-01-03 02:46:27356 } else if (this.internalZoom_ == 0) {
Henrique Nakashima4cf9ed42018-09-07 21:02:03357 this.fitToNone();
Dan Beamd1cca6e2019-01-03 02:46:27358 } else {
[email protected]312112c72014-04-14 01:45:43359 this.updateViewport_();
Dan Beamd1cca6e2019-01-03 02:46:27360 }
[email protected]312112c72014-04-14 01:45:43361 },
362
363 /**
Douglas Stockwell1752f7762018-11-28 23:41:36364 * @type {Point} the scroll position of the viewport.
[email protected]312112c72014-04-14 01:45:43365 */
366 get position() {
367 return {
368 x: this.window_.pageXOffset,
raymesbd82a942015-08-05 07:05:18369 y: this.window_.pageYOffset - this.topToolbarHeight_
[email protected]312112c72014-04-14 01:45:43370 };
371 },
372
373 /**
374 * Scroll the viewport to the specified position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42375 *
Douglas Stockwell1752f7762018-11-28 23:41:36376 * @type {Point} position The position to scroll to.
[email protected]312112c72014-04-14 01:45:43377 */
378 set position(position) {
raymesbd82a942015-08-05 07:05:18379 this.window_.scrollTo(position.x, position.y + this.topToolbarHeight_);
[email protected]312112c72014-04-14 01:45:43380 },
381
382 /**
383 * @type {Object} the size of the viewport excluding scrollbars.
384 */
385 get size() {
dstockwellafba4e42018-12-02 22:58:06386 const needsScrollbars = this.documentNeedsScrollbars_(this.zoom);
387 const scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0;
388 const scrollbarHeight =
389 needsScrollbars.horizontal ? this.scrollbarWidth_ : 0;
[email protected]312112c72014-04-14 01:45:43390 return {
391 width: this.window_.innerWidth - scrollbarWidth,
392 height: this.window_.innerHeight - scrollbarHeight
393 };
394 },
395
396 /**
397 * @type {number} the zoom level of the viewport.
398 */
399 get zoom() {
mcnee2999413a2016-12-06 20:29:25400 return this.zoomManager_.applyBrowserZoom(this.internalZoom_);
401 },
402
403 /**
404 * Set the zoom manager.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42405 *
mcnee2999413a2016-12-06 20:29:25406 * @type {ZoomManager} manager the zoom manager to set.
407 */
408 set zoomManager(manager) {
409 this.zoomManager_ = manager;
[email protected]312112c72014-04-14 01:45:43410 },
411
412 /**
mcnee6e1abbf2016-11-09 18:05:31413 * @type {Viewport.PinchPhase} The phase of the current pinch gesture for
414 * the viewport.
415 */
416 get pinchPhase() {
417 return this.pinchPhase_;
418 },
419
420 /**
421 * @type {Object} The panning caused by the current pinch gesture (as
422 * the deltas of the x and y coordinates).
423 */
424 get pinchPanVector() {
425 return this.pinchPanVector_;
426 },
427
428 /**
429 * @type {Object} The coordinates of the center of the current pinch gesture.
430 */
431 get pinchCenter() {
432 return this.pinchCenter_;
433 },
434
435 /**
[email protected]499e9562014-06-26 05:45:27436 * Used to wrap a function that might perform zooming on the viewport. This is
437 * required so that we can notify the plugin that zooming is in progress
438 * so that while zooming is taking place it can stop reacting to scroll events
439 * from the viewport. This is to avoid flickering.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42440 *
Douglas Stockwell1752f7762018-11-28 23:41:36441 * @param {Function} f Function to wrap
Henrique Nakashimad5de6d0d2018-04-13 18:02:42442 * @private
[email protected]499e9562014-06-26 05:45:27443 */
444 mightZoom_: function(f) {
445 this.beforeZoomCallback_();
446 this.allowedToChangeZoom_ = true;
447 f();
448 this.allowedToChangeZoom_ = false;
449 this.afterZoomCallback_();
450 },
451
452 /**
[email protected]312112c72014-04-14 01:45:43453 * Sets the zoom of the viewport.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42454 *
[email protected]312112c72014-04-14 01:45:43455 * @param {number} newZoom the zoom level to zoom to.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42456 * @private
[email protected]312112c72014-04-14 01:45:43457 */
[email protected]fbad5bb2014-07-18 07:20:36458 setZoomInternal_: function(newZoom) {
dstockwell154f9c42019-01-01 23:36:34459 assert(
460 this.allowedToChangeZoom_,
461 'Called Viewport.setZoomInternal_ without calling ' +
462 'Viewport.mightZoom_.');
sammc5c33b7e2014-11-07 04:03:09463 // Record the scroll position (relative to the top-left of the window).
dstockwellafba4e42018-12-02 22:58:06464 const currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25465 x: this.position.x / this.zoom,
466 y: this.position.y / this.zoom
raymesbd82a942015-08-05 07:05:18467 };
Henrique Nakashima4cf9ed42018-09-07 21:02:03468
mcnee2999413a2016-12-06 20:29:25469 this.internalZoom_ = newZoom;
[email protected]3528d6302014-02-19 08:13:07470 this.contentSizeChanged_();
471 // Scroll to the scaled scroll position.
raymesbd82a942015-08-05 07:05:18472 this.position = {
mcnee2999413a2016-12-06 20:29:25473 x: currentScrollPos.x * this.zoom,
474 y: currentScrollPos.y * this.zoom
raymesbd82a942015-08-05 07:05:18475 };
[email protected]3528d6302014-02-19 08:13:07476 },
477
478 /**
mcnee6e1abbf2016-11-09 18:05:31479 * Sets the zoom of the viewport.
480 * Same as setZoomInternal_ but for pinch zoom we have some more operations.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42481 *
mcnee6e1abbf2016-11-09 18:05:31482 * @param {number} scaleDelta The zoom delta.
483 * @param {!Object} center The pinch center in content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42484 * @private
mcnee6e1abbf2016-11-09 18:05:31485 */
486 setPinchZoomInternal_: function(scaleDelta, center) {
dbeam70db0fb2017-06-19 17:09:27487 assert(
488 this.allowedToChangeZoom_,
mcnee6e1abbf2016-11-09 18:05:31489 'Called Viewport.setPinchZoomInternal_ without calling ' +
dbeam70db0fb2017-06-19 17:09:27490 'Viewport.mightZoom_.');
Kevin McNee4e78edc2017-10-26 19:55:54491 this.internalZoom_ = Viewport.clampZoom(this.internalZoom_ * scaleDelta);
mcnee6e1abbf2016-11-09 18:05:31492
dstockwellafba4e42018-12-02 22:58:06493 const newCenterInContent = this.frameToContent(center);
494 const delta = {
mcnee6e1abbf2016-11-09 18:05:31495 x: (newCenterInContent.x - this.oldCenterInContent.x),
496 y: (newCenterInContent.y - this.oldCenterInContent.y)
497 };
498
499 // Record the scroll position (relative to the pinch center).
dstockwellafba4e42018-12-02 22:58:06500 const currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25501 x: this.position.x - delta.x * this.zoom,
502 y: this.position.y - delta.y * this.zoom
mcnee6e1abbf2016-11-09 18:05:31503 };
504
505 this.contentSizeChanged_();
506 // Scroll to the scaled scroll position.
dbeam70db0fb2017-06-19 17:09:27507 this.position = {x: currentScrollPos.x, y: currentScrollPos.y};
mcnee6e1abbf2016-11-09 18:05:31508 },
509
510 /**
mcnee6e1abbf2016-11-09 18:05:31511 * Converts a point from frame to content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42512 *
mcnee6e1abbf2016-11-09 18:05:31513 * @param {!Object} framePoint The frame coordinates.
514 * @return {!Object} The content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42515 * @private
mcnee6e1abbf2016-11-09 18:05:31516 */
517 frameToContent: function(framePoint) {
518 // TODO(mcnee) Add a helper Point class to avoid duplicating operations
519 // on plain {x,y} objects.
520 return {
mcnee2999413a2016-12-06 20:29:25521 x: (framePoint.x + this.position.x) / this.zoom,
522 y: (framePoint.y + this.position.y) / this.zoom
mcnee6e1abbf2016-11-09 18:05:31523 };
524 },
525
526 /**
[email protected]fbad5bb2014-07-18 07:20:36527 * Sets the zoom to the given zoom level.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42528 *
[email protected]fbad5bb2014-07-18 07:20:36529 * @param {number} newZoom the zoom level to zoom to.
[email protected]499e9562014-06-26 05:45:27530 */
[email protected]fbad5bb2014-07-18 07:20:36531 setZoom: function(newZoom) {
Henrique Nakashima50b18e02017-11-21 23:29:57532 this.fittingType_ = FittingType.NONE;
dpapad9afc2802017-08-09 22:01:43533 this.mightZoom_(() => {
Kevin McNee4e78edc2017-10-26 19:55:54534 this.setZoomInternal_(Viewport.clampZoom(newZoom));
[email protected]fbad5bb2014-07-18 07:20:36535 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43536 });
[email protected]499e9562014-06-26 05:45:27537 },
538
539 /**
mcnee2999413a2016-12-06 20:29:25540 * Gets notified of the browser zoom changing seperately from the
541 * internal zoom.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42542 *
mcnee2999413a2016-12-06 20:29:25543 * @param {number} oldBrowserZoom the previous value of the browser zoom.
544 */
545 updateZoomFromBrowserChange: function(oldBrowserZoom) {
dpapad9afc2802017-08-09 22:01:43546 this.mightZoom_(() => {
mcnee2999413a2016-12-06 20:29:25547 // Record the scroll position (relative to the top-left of the window).
dstockwellafba4e42018-12-02 22:58:06548 const oldZoom = oldBrowserZoom * this.internalZoom_;
549 const currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25550 x: this.position.x / oldZoom,
551 y: this.position.y / oldZoom
552 };
553 this.contentSizeChanged_();
554 // Scroll to the scaled scroll position.
555 this.position = {
556 x: currentScrollPos.x * this.zoom,
557 y: currentScrollPos.y * this.zoom
558 };
559 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43560 });
mcnee2999413a2016-12-06 20:29:25561 },
562
563 /**
[email protected]312112c72014-04-14 01:45:43564 * @type {number} the width of scrollbars in the viewport in pixels.
[email protected]3528d6302014-02-19 08:13:07565 */
[email protected]312112c72014-04-14 01:45:43566 get scrollbarWidth() {
567 return this.scrollbarWidth_;
[email protected]3528d6302014-02-19 08:13:07568 },
569
570 /**
Henrique Nakashima50b18e02017-11-21 23:29:57571 * @type {FittingType} the fitting type the viewport is currently in.
[email protected]3528d6302014-02-19 08:13:07572 */
[email protected]312112c72014-04-14 01:45:43573 get fittingType() {
574 return this.fittingType_;
[email protected]3528d6302014-02-19 08:13:07575 },
576
577 /**
Henrique Nakashimad5de6d0d2018-04-13 18:02:42578 * Get the which page is at a given y position.
579 *
dpapadd2d64772017-05-19 02:30:38580 * @param {number} y the y-coordinate to get the page at.
581 * @return {number} the index of a page overlapping the given y-coordinate.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42582 * @private
[email protected]56b7e3c2014-02-20 04:31:24583 */
584 getPageAtY_: function(y) {
dstockwellafba4e42018-12-02 22:58:06585 let min = 0;
586 let max = this.pageDimensions_.length - 1;
[email protected]56b7e3c2014-02-20 04:31:24587 while (max >= min) {
dstockwellafba4e42018-12-02 22:58:06588 const page = Math.floor(min + ((max - min) / 2));
[email protected]13df2a42014-02-27 03:50:41589 // There might be a gap between the pages, in which case use the bottom
590 // of the previous page as the top for finding the page.
dstockwellafba4e42018-12-02 22:58:06591 let top = 0;
[email protected]13df2a42014-02-27 03:50:41592 if (page > 0) {
593 top = this.pageDimensions_[page - 1].y +
594 this.pageDimensions_[page - 1].height;
595 }
dstockwellafba4e42018-12-02 22:58:06596 const bottom =
dbeam70db0fb2017-06-19 17:09:27597 this.pageDimensions_[page].y + this.pageDimensions_[page].height;
[email protected]13df2a42014-02-27 03:50:41598
Dan Beamd1cca6e2019-01-03 02:46:27599 if (top <= y && bottom > y) {
[email protected]56b7e3c2014-02-20 04:31:24600 return page;
Dan Beamd1cca6e2019-01-03 02:46:27601 }
Lei Zhangddc0ef032017-11-22 02:14:24602
Dan Beamd1cca6e2019-01-03 02:46:27603 if (top > y) {
[email protected]56b7e3c2014-02-20 04:31:24604 max = page - 1;
Dan Beamd1cca6e2019-01-03 02:46:27605 } else {
[email protected]56b7e3c2014-02-20 04:31:24606 min = page + 1;
Dan Beamd1cca6e2019-01-03 02:46:27607 }
[email protected]56b7e3c2014-02-20 04:31:24608 }
609 return 0;
610 },
611
612 /**
dstockwell09815ba2019-01-16 06:44:24613 * @param {Point} point
614 * @return {boolean} Whether |point| (in screen coordinates) is inside a page
615 */
616 isPointInsidePage(point) {
617 const zoom = this.zoom;
618 const size = this.size;
619 const position = this.position;
620 const page = this.getPageAtY_((position.y + point.y) / zoom);
621 const pageWidth = this.pageDimensions_[page].width * zoom;
622 const documentWidth = this.getDocumentDimensions().width * zoom;
623
624 const outerWidth = Math.max(size.width, documentWidth);
625
626 if (pageWidth >= outerWidth) {
627 return true;
628 }
629
630 const x = point.x + position.x;
631
632 const minX = (outerWidth - pageWidth) / 2;
633 const maxX = outerWidth - minX;
634 return x >= minX && x <= maxX;
635 },
636
637 /**
sammcd06823a962014-11-10 04:51:15638 * Returns the page with the greatest proportion of its height in the current
639 * viewport.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42640 *
dpapadd2d64772017-05-19 02:30:38641 * @return {number} the index of the most visible page.
[email protected]3528d6302014-02-19 08:13:07642 */
643 getMostVisiblePage: function() {
dstockwellafba4e42018-12-02 22:58:06644 const firstVisiblePage = this.getPageAtY_(this.position.y / this.zoom);
Dan Beamd1cca6e2019-01-03 02:46:27645 if (firstVisiblePage == this.pageDimensions_.length - 1) {
sammcd06823a962014-11-10 04:51:15646 return firstVisiblePage;
Dan Beamd1cca6e2019-01-03 02:46:27647 }
sammcd06823a962014-11-10 04:51:15648
dstockwellafba4e42018-12-02 22:58:06649 const viewportRect = {
mcnee2999413a2016-12-06 20:29:25650 x: this.position.x / this.zoom,
651 y: this.position.y / this.zoom,
652 width: this.size.width / this.zoom,
653 height: this.size.height / this.zoom
[email protected]345e7c62014-05-02 09:52:58654 };
dstockwellafba4e42018-12-02 22:58:06655 const firstVisiblePageVisibility =
dbeam70db0fb2017-06-19 17:09:27656 getIntersectionHeight(
657 this.pageDimensions_[firstVisiblePage], viewportRect) /
sammcd06823a962014-11-10 04:51:15658 this.pageDimensions_[firstVisiblePage].height;
dstockwellafba4e42018-12-02 22:58:06659 const nextPageVisibility =
dbeam70db0fb2017-06-19 17:09:27660 getIntersectionHeight(
661 this.pageDimensions_[firstVisiblePage + 1], viewportRect) /
sammcd06823a962014-11-10 04:51:15662 this.pageDimensions_[firstVisiblePage + 1].height;
Dan Beamd1cca6e2019-01-03 02:46:27663 if (nextPageVisibility > firstVisiblePageVisibility) {
sammcd06823a962014-11-10 04:51:15664 return firstVisiblePage + 1;
Dan Beamd1cca6e2019-01-03 02:46:27665 }
sammcd06823a962014-11-10 04:51:15666 return firstVisiblePage;
[email protected]3528d6302014-02-19 08:13:07667 },
668
669 /**
Henrique Nakashima50b18e02017-11-21 23:29:57670 * Compute the zoom level for fit-to-page, fit-to-width or fit-to-height.
671 *
672 * At least one of {fitWidth, fitHeight} must be true.
673 *
674 * @param {Object} pageDimensions the dimensions of a given page in px.
675 * @param {boolean} fitWidth a bool indicating whether the whole width of the
676 * page needs to be in the viewport.
677 * @param {boolean} fitHeight a bool indicating whether the whole height of
678 * the page needs to be in the viewport.
mcnee2999413a2016-12-06 20:29:25679 * @return {number} the internal zoom to set
Henrique Nakashimad5de6d0d2018-04-13 18:02:42680 * @private
[email protected]3528d6302014-02-19 08:13:07681 */
Henrique Nakashima50b18e02017-11-21 23:29:57682 computeFittingZoom_: function(pageDimensions, fitWidth, fitHeight) {
683 assert(
684 fitWidth || fitHeight,
685 'Invalid parameters. At least one of fitWidth and fitHeight must be ' +
686 'true.');
687
[email protected]3528d6302014-02-19 08:13:07688 // First compute the zoom without scrollbars.
dstockwellafba4e42018-12-02 22:58:06689 let zoom = this.computeFittingZoomGivenDimensions_(
Henrique Nakashima50b18e02017-11-21 23:29:57690 fitWidth, fitHeight, this.window_.innerWidth, this.window_.innerHeight,
691 pageDimensions.width, pageDimensions.height);
692
[email protected]3528d6302014-02-19 08:13:07693 // Check if there needs to be any scrollbars.
dstockwellafba4e42018-12-02 22:58:06694 const needsScrollbars = this.documentNeedsScrollbars_(zoom);
[email protected]3528d6302014-02-19 08:13:07695
696 // If the document fits, just return the zoom.
Dan Beamd1cca6e2019-01-03 02:46:27697 if (!needsScrollbars.horizontal && !needsScrollbars.vertical) {
[email protected]3528d6302014-02-19 08:13:07698 return zoom;
Dan Beamd1cca6e2019-01-03 02:46:27699 }
[email protected]3528d6302014-02-19 08:13:07700
dstockwellafba4e42018-12-02 22:58:06701 const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
[email protected]3528d6302014-02-19 08:13:07702
703 // Check if adding a scrollbar will result in needing the other scrollbar.
dstockwellafba4e42018-12-02 22:58:06704 const scrollbarWidth = this.scrollbarWidth_;
[email protected]312112c72014-04-14 01:45:43705 if (needsScrollbars.horizontal &&
[email protected]3528d6302014-02-19 08:13:07706 zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43707 needsScrollbars.vertical = true;
[email protected]3528d6302014-02-19 08:13:07708 }
[email protected]312112c72014-04-14 01:45:43709 if (needsScrollbars.vertical &&
[email protected]3528d6302014-02-19 08:13:07710 zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43711 needsScrollbars.horizontal = true;
[email protected]3528d6302014-02-19 08:13:07712 }
713
714 // Compute available window space.
dstockwellafba4e42018-12-02 22:58:06715 const windowWithScrollbars = {
[email protected]3528d6302014-02-19 08:13:07716 width: this.window_.innerWidth,
717 height: this.window_.innerHeight
718 };
Dan Beamd1cca6e2019-01-03 02:46:27719 if (needsScrollbars.horizontal) {
[email protected]3528d6302014-02-19 08:13:07720 windowWithScrollbars.height -= scrollbarWidth;
Dan Beamd1cca6e2019-01-03 02:46:27721 }
722 if (needsScrollbars.vertical) {
[email protected]3528d6302014-02-19 08:13:07723 windowWithScrollbars.width -= scrollbarWidth;
Dan Beamd1cca6e2019-01-03 02:46:27724 }
[email protected]3528d6302014-02-19 08:13:07725
726 // Recompute the zoom.
Henrique Nakashima50b18e02017-11-21 23:29:57727 zoom = this.computeFittingZoomGivenDimensions_(
728 fitWidth, fitHeight, windowWithScrollbars.width,
729 windowWithScrollbars.height, pageDimensions.width,
730 pageDimensions.height);
731
mcnee2999413a2016-12-06 20:29:25732 return this.zoomManager_.internalZoomComponent(zoom);
[email protected]3528d6302014-02-19 08:13:07733 },
734
735 /**
Henrique Nakashima50b18e02017-11-21 23:29:57736 * Compute a zoom level given the dimensions to fit and the actual numbers
737 * in those dimensions.
738 *
739 * @param {boolean} fitWidth make sure the page width is totally contained in
740 * the window.
741 * @param {boolean} fitHeight make sure the page height is totally contained
742 * in the window.
743 * @param {number} windowWidth the width of the window in px.
744 * @param {number} windowHeight the height of the window in px.
745 * @param {number} pageWidth the width of the page in px.
746 * @param {number} pageHeight the height of the page in px.
747 * @return {number} the internal zoom to set
Henrique Nakashimad5de6d0d2018-04-13 18:02:42748 * @private
Henrique Nakashima50b18e02017-11-21 23:29:57749 */
750 computeFittingZoomGivenDimensions_: function(
751 fitWidth, fitHeight, windowWidth, windowHeight, pageWidth, pageHeight) {
752 // Assumes at least one of {fitWidth, fitHeight} is set.
dstockwellafba4e42018-12-02 22:58:06753 let zoomWidth;
754 let zoomHeight;
Henrique Nakashima50b18e02017-11-21 23:29:57755
Dan Beamd1cca6e2019-01-03 02:46:27756 if (fitWidth) {
Henrique Nakashima50b18e02017-11-21 23:29:57757 zoomWidth = windowWidth / pageWidth;
Dan Beamd1cca6e2019-01-03 02:46:27758 }
Henrique Nakashima50b18e02017-11-21 23:29:57759
Dan Beamd1cca6e2019-01-03 02:46:27760 if (fitHeight) {
Henrique Nakashima50b18e02017-11-21 23:29:57761 zoomHeight = windowHeight / pageHeight;
Dan Beamd1cca6e2019-01-03 02:46:27762 }
Henrique Nakashima50b18e02017-11-21 23:29:57763
dstockwellafba4e42018-12-02 22:58:06764 let zoom;
Henrique Nakashima4cf9ed42018-09-07 21:02:03765 if (!fitWidth && fitHeight) {
766 zoom = zoomHeight;
767 } else if (fitWidth && !fitHeight) {
768 zoom = zoomWidth;
769 } else {
770 // Assume fitWidth && fitHeight
771 zoom = Math.min(zoomWidth, zoomHeight);
772 }
Henrique Nakashima50b18e02017-11-21 23:29:57773
Henrique Nakashima4cf9ed42018-09-07 21:02:03774 return Math.max(zoom, 0);
Henrique Nakashima50b18e02017-11-21 23:29:57775 },
776
777 /**
778 * Zoom the viewport so that the page width consumes the entire viewport.
[email protected]3528d6302014-02-19 08:13:07779 */
780 fitToWidth: function() {
dpapad9afc2802017-08-09 22:01:43781 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57782 this.fittingType_ = FittingType.FIT_TO_WIDTH;
Dan Beamd1cca6e2019-01-03 02:46:27783 if (!this.documentDimensions_) {
[email protected]499e9562014-06-26 05:45:27784 return;
Dan Beamd1cca6e2019-01-03 02:46:27785 }
[email protected]499e9562014-06-26 05:45:27786 // When computing fit-to-width, the maximum width of a page in the
787 // document is used, which is equal to the size of the document width.
dbeam70db0fb2017-06-19 17:09:27788 this.setZoomInternal_(
Henrique Nakashima50b18e02017-11-21 23:29:57789 this.computeFittingZoom_(this.documentDimensions_, true, false));
[email protected]499e9562014-06-26 05:45:27790 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43791 });
[email protected]3528d6302014-02-19 08:13:07792 },
793
794 /**
Henrique Nakashima50b18e02017-11-21 23:29:57795 * Zoom the viewport so that the page height consumes the entire viewport.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42796 *
Henrique Nakashima50b18e02017-11-21 23:29:57797 * @param {boolean} scrollToTopOfPage Set to true if the viewport should be
798 * scrolled to the top of the current page. Set to false if the viewport
799 * should remain at the current scroll position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42800 * @private
Henrique Nakashima50b18e02017-11-21 23:29:57801 */
802 fitToHeightInternal_: function(scrollToTopOfPage) {
803 this.mightZoom_(() => {
804 this.fittingType_ = FittingType.FIT_TO_HEIGHT;
Dan Beamd1cca6e2019-01-03 02:46:27805 if (!this.documentDimensions_) {
Henrique Nakashima50b18e02017-11-21 23:29:57806 return;
Dan Beamd1cca6e2019-01-03 02:46:27807 }
dstockwellafba4e42018-12-02 22:58:06808 const page = this.getMostVisiblePage();
Henrique Nakashima50b18e02017-11-21 23:29:57809 // When computing fit-to-height, the maximum height of the current page
810 // is used.
dstockwellafba4e42018-12-02 22:58:06811 const dimensions = {
Henrique Nakashima50b18e02017-11-21 23:29:57812 width: 0,
813 height: this.pageDimensions_[page].height,
814 };
815 this.setZoomInternal_(this.computeFittingZoom_(dimensions, false, true));
816 if (scrollToTopOfPage) {
817 this.position = {x: 0, y: this.pageDimensions_[page].y * this.zoom};
818 }
819 this.updateViewport_();
820 });
821 },
822
823 /**
824 * Zoom the viewport so that the page height consumes the entire viewport.
825 */
826 fitToHeight: function() {
827 this.fitToHeightInternal_(true);
828 },
829
830 /**
Henrique Nakashima50b18e02017-11-21 23:29:57831 * Zoom the viewport so that a page consumes as much as possible of the it.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42832 *
raymes161514f2015-02-17 05:09:51833 * @param {boolean} scrollToTopOfPage Set to true if the viewport should be
834 * scrolled to the top of the current page. Set to false if the viewport
835 * should remain at the current scroll position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42836 * @private
[email protected]3528d6302014-02-19 08:13:07837 */
raymes161514f2015-02-17 05:09:51838 fitToPageInternal_: function(scrollToTopOfPage) {
dpapad9afc2802017-08-09 22:01:43839 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57840 this.fittingType_ = FittingType.FIT_TO_PAGE;
Dan Beamd1cca6e2019-01-03 02:46:27841 if (!this.documentDimensions_) {
[email protected]499e9562014-06-26 05:45:27842 return;
Dan Beamd1cca6e2019-01-03 02:46:27843 }
dstockwellafba4e42018-12-02 22:58:06844 const page = this.getMostVisiblePage();
sammc07f53aa2014-11-06 06:57:46845 // Fit to the current page's height and the widest page's width.
dstockwellafba4e42018-12-02 22:58:06846 const dimensions = {
sammc07f53aa2014-11-06 06:57:46847 width: this.documentDimensions_.width,
848 height: this.pageDimensions_[page].height,
849 };
Henrique Nakashima50b18e02017-11-21 23:29:57850 this.setZoomInternal_(this.computeFittingZoom_(dimensions, true, true));
raymesbd82a942015-08-05 07:05:18851 if (scrollToTopOfPage) {
dbeam70db0fb2017-06-19 17:09:27852 this.position = {x: 0, y: this.pageDimensions_[page].y * this.zoom};
raymesbd82a942015-08-05 07:05:18853 }
[email protected]499e9562014-06-26 05:45:27854 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43855 });
[email protected]3528d6302014-02-19 08:13:07856 },
857
858 /**
raymes161514f2015-02-17 05:09:51859 * Zoom the viewport so that a page consumes the entire viewport. Also scrolls
860 * the viewport to the top of the current page.
861 */
862 fitToPage: function() {
863 this.fitToPageInternal_(true);
864 },
865
866 /**
Henrique Nakashima4cf9ed42018-09-07 21:02:03867 * Zoom the viewport to the default zoom policy.
868 */
869 fitToNone: function() {
870 this.mightZoom_(() => {
871 this.fittingType_ = FittingType.NONE;
Dan Beamd1cca6e2019-01-03 02:46:27872 if (!this.documentDimensions_) {
Henrique Nakashima4cf9ed42018-09-07 21:02:03873 return;
Dan Beamd1cca6e2019-01-03 02:46:27874 }
Henrique Nakashima4cf9ed42018-09-07 21:02:03875 this.setZoomInternal_(Math.min(
876 this.defaultZoom_,
877 this.computeFittingZoom_(this.documentDimensions_, true, false)));
878 this.updateViewport_();
879 });
880 },
881
882 /**
[email protected]3528d6302014-02-19 08:13:07883 * Zoom out to the next predefined zoom level.
884 */
885 zoomOut: function() {
dpapad9afc2802017-08-09 22:01:43886 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57887 this.fittingType_ = FittingType.NONE;
dstockwellafba4e42018-12-02 22:58:06888 let nextZoom = Viewport.ZOOM_FACTORS[0];
889 for (let i = 0; i < Viewport.ZOOM_FACTORS.length; i++) {
Dan Beamd1cca6e2019-01-03 02:46:27890 if (Viewport.ZOOM_FACTORS[i] < this.internalZoom_) {
[email protected]499e9562014-06-26 05:45:27891 nextZoom = Viewport.ZOOM_FACTORS[i];
Dan Beamd1cca6e2019-01-03 02:46:27892 }
[email protected]499e9562014-06-26 05:45:27893 }
[email protected]fbad5bb2014-07-18 07:20:36894 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27895 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43896 });
[email protected]3528d6302014-02-19 08:13:07897 },
898
899 /**
900 * Zoom in to the next predefined zoom level.
901 */
902 zoomIn: function() {
dpapad9afc2802017-08-09 22:01:43903 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57904 this.fittingType_ = FittingType.NONE;
dstockwellafba4e42018-12-02 22:58:06905 let nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1];
906 for (let i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) {
Dan Beamd1cca6e2019-01-03 02:46:27907 if (Viewport.ZOOM_FACTORS[i] > this.internalZoom_) {
[email protected]499e9562014-06-26 05:45:27908 nextZoom = Viewport.ZOOM_FACTORS[i];
Dan Beamd1cca6e2019-01-03 02:46:27909 }
[email protected]499e9562014-06-26 05:45:27910 }
[email protected]fbad5bb2014-07-18 07:20:36911 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27912 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43913 });
[email protected]3528d6302014-02-19 08:13:07914 },
915
916 /**
mcnee6e1abbf2016-11-09 18:05:31917 * Pinch zoom event handler.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42918 *
mcnee6e1abbf2016-11-09 18:05:31919 * @param {!Object} e The pinch event.
920 */
921 pinchZoom: function(e) {
dpapad9afc2802017-08-09 22:01:43922 this.mightZoom_(() => {
mcnee6e1abbf2016-11-09 18:05:31923 this.pinchPhase_ = e.direction == 'out' ?
dbeam70db0fb2017-06-19 17:09:27924 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_OUT :
925 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_IN;
mcnee6e1abbf2016-11-09 18:05:31926
dstockwellafba4e42018-12-02 22:58:06927 const scaleDelta = e.startScaleRatio / this.prevScale_;
mcnee6e1abbf2016-11-09 18:05:31928 this.pinchPanVector_ =
929 vectorDelta(e.center, this.firstPinchCenterInFrame_);
930
dstockwellafba4e42018-12-02 22:58:06931 const needsScrollbars =
dbeam70db0fb2017-06-19 17:09:27932 this.documentNeedsScrollbars_(this.zoomManager_.applyBrowserZoom(
Kevin McNee4e78edc2017-10-26 19:55:54933 Viewport.clampZoom(this.internalZoom_ * scaleDelta)));
mcnee6e1abbf2016-11-09 18:05:31934
935 this.pinchCenter_ = e.center;
936
937 // If there's no horizontal scrolling, keep the content centered so the
938 // user can't zoom in on the non-content area.
939 // TODO(mcnee) Investigate other ways of scaling when we don't have
940 // horizontal scrolling. We want to keep the document centered,
941 // but this causes a potentially awkward transition when we start
942 // using the gesture center.
943 if (!needsScrollbars.horizontal) {
944 this.pinchCenter_ = {
945 x: this.window_.innerWidth / 2,
946 y: this.window_.innerHeight / 2
947 };
948 } else if (this.keepContentCentered_) {
949 this.oldCenterInContent =
950 this.frameToContent(frameToPluginCoordinate(e.center));
951 this.keepContentCentered_ = false;
952 }
953
dbeam70db0fb2017-06-19 17:09:27954 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:31955 this.updateViewport_();
956 this.prevScale_ = e.startScaleRatio;
dpapad9afc2802017-08-09 22:01:43957 });
mcnee6e1abbf2016-11-09 18:05:31958 },
959
960 pinchZoomStart: function(e) {
961 this.pinchPhase_ = Viewport.PinchPhase.PINCH_START;
962 this.prevScale_ = 1;
963 this.oldCenterInContent =
964 this.frameToContent(frameToPluginCoordinate(e.center));
965
dstockwellafba4e42018-12-02 22:58:06966 const needsScrollbars = this.documentNeedsScrollbars_(this.zoom);
mcnee6e1abbf2016-11-09 18:05:31967 this.keepContentCentered_ = !needsScrollbars.horizontal;
968 // We keep track of begining of the pinch.
969 // By doing so we will be able to compute the pan distance.
970 this.firstPinchCenterInFrame_ = e.center;
971 },
972
973 pinchZoomEnd: function(e) {
dpapad9afc2802017-08-09 22:01:43974 this.mightZoom_(() => {
mcnee6e1abbf2016-11-09 18:05:31975 this.pinchPhase_ = Viewport.PinchPhase.PINCH_END;
dstockwellafba4e42018-12-02 22:58:06976 const scaleDelta = e.startScaleRatio / this.prevScale_;
mcnee6e1abbf2016-11-09 18:05:31977 this.pinchCenter_ = e.center;
978
dbeam70db0fb2017-06-19 17:09:27979 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:31980 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43981 });
mcnee6e1abbf2016-11-09 18:05:31982
983 this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
984 this.pinchPanVector_ = null;
985 this.pinchCenter_ = null;
986 this.firstPinchCenterInFrame_ = null;
987 },
988
989 /**
[email protected]3528d6302014-02-19 08:13:07990 * Go to the given page index.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42991 *
[email protected]f90b9a42014-08-20 05:37:34992 * @param {number} page the index of the page to go to. zero-based.
[email protected]3528d6302014-02-19 08:13:07993 */
994 goToPage: function(page) {
Henrique Nakashima85fc58b32017-12-11 21:53:42995 this.goToPageAndXY(page, 0, 0);
Henrique Nakashima9d9e0632017-10-06 21:38:18996 },
997
998 /**
999 * Go to the given y position in the given page index.
Henrique Nakashimad5de6d0d2018-04-13 18:02:421000 *
Henrique Nakashima9d9e0632017-10-06 21:38:181001 * @param {number} page the index of the page to go to. zero-based.
Henrique Nakashima85fc58b32017-12-11 21:53:421002 * @param {number} x the x position in the page to go to.
Henrique Nakashima9d9e0632017-10-06 21:38:181003 * @param {number} y the y position in the page to go to.
1004 */
Henrique Nakashima85fc58b32017-12-11 21:53:421005 goToPageAndXY: function(page, x, y) {
dpapad9afc2802017-08-09 22:01:431006 this.mightZoom_(() => {
Dan Beamd1cca6e2019-01-03 02:46:271007 if (this.pageDimensions_.length === 0) {
[email protected]499e9562014-06-26 05:45:271008 return;
Dan Beamd1cca6e2019-01-03 02:46:271009 }
1010 if (page < 0) {
[email protected]499e9562014-06-26 05:45:271011 page = 0;
Dan Beamd1cca6e2019-01-03 02:46:271012 }
1013 if (page >= this.pageDimensions_.length) {
[email protected]499e9562014-06-26 05:45:271014 page = this.pageDimensions_.length - 1;
Dan Beamd1cca6e2019-01-03 02:46:271015 }
dstockwellafba4e42018-12-02 22:58:061016 const dimensions = this.pageDimensions_[page];
1017 let toolbarOffset = 0;
Henrique Nakashima50b18e02017-11-21 23:29:571018 // Unless we're in fit to page or fit to height mode, scroll above the
1019 // page by |this.topToolbarHeight_| so that the toolbar isn't covering it
raymesbd82a942015-08-05 07:05:181020 // initially.
Dan Beamd1cca6e2019-01-03 02:46:271021 if (!this.isPagedMode()) {
raymesbd82a942015-08-05 07:05:181022 toolbarOffset = this.topToolbarHeight_;
Dan Beamd1cca6e2019-01-03 02:46:271023 }
raymesbd82a942015-08-05 07:05:181024 this.position = {
Henrique Nakashima85fc58b32017-12-11 21:53:421025 x: (dimensions.x + x) * this.zoom,
Henrique Nakashima9d9e0632017-10-06 21:38:181026 y: (dimensions.y + y) * this.zoom - toolbarOffset
raymesbd82a942015-08-05 07:05:181027 };
[email protected]499e9562014-06-26 05:45:271028 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:431029 });
[email protected]3528d6302014-02-19 08:13:071030 },
1031
1032 /**
1033 * Set the dimensions of the document.
Henrique Nakashimad5de6d0d2018-04-13 18:02:421034 *
[email protected]3528d6302014-02-19 08:13:071035 * @param {Object} documentDimensions the dimensions of the document
1036 */
1037 setDocumentDimensions: function(documentDimensions) {
dpapad9afc2802017-08-09 22:01:431038 this.mightZoom_(() => {
dstockwellafba4e42018-12-02 22:58:061039 const initialDimensions = !this.documentDimensions_;
[email protected]499e9562014-06-26 05:45:271040 this.documentDimensions_ = documentDimensions;
1041 this.pageDimensions_ = this.documentDimensions_.pageDimensions;
1042 if (initialDimensions) {
dbeam70db0fb2017-06-19 17:09:271043 this.setZoomInternal_(Math.min(
1044 this.defaultZoom_,
Henrique Nakashima50b18e02017-11-21 23:29:571045 this.computeFittingZoom_(this.documentDimensions_, true, false)));
dbeam70db0fb2017-06-19 17:09:271046 this.position = {x: 0, y: -this.topToolbarHeight_};
[email protected]499e9562014-06-26 05:45:271047 }
1048 this.contentSizeChanged_();
1049 this.resize_();
dpapad9afc2802017-08-09 22:01:431050 });
[email protected]312112c72014-04-14 01:45:431051 },
1052
1053 /**
Douglas Stockwell1752f7762018-11-28 23:41:361054 * @param {number} page
1055 * @return {Rect} The bounds for page `page` minus the shadows.
1056 */
1057 getPageInsetDimensions: function(page) {
1058 const pageDimensions = this.pageDimensions_[page];
1059 const shadow = Viewport.PAGE_SHADOW;
1060 return {
1061 x: pageDimensions.x + shadow.left,
1062 y: pageDimensions.y + shadow.top,
1063 width: pageDimensions.width - shadow.left - shadow.right,
1064 height: pageDimensions.height - shadow.top - shadow.bottom,
1065 };
1066 },
1067
1068 /**
[email protected]312112c72014-04-14 01:45:431069 * Get the coordinates of the page contents (excluding the page shadow)
1070 * relative to the screen.
Henrique Nakashimad5de6d0d2018-04-13 18:02:421071 *
[email protected]312112c72014-04-14 01:45:431072 * @param {number} page the index of the page to get the rect for.
1073 * @return {Object} a rect representing the page in screen coordinates.
1074 */
1075 getPageScreenRect: function(page) {
[email protected]499e9562014-06-26 05:45:271076 if (!this.documentDimensions_) {
dbeam70db0fb2017-06-19 17:09:271077 return {x: 0, y: 0, width: 0, height: 0};
[email protected]499e9562014-06-26 05:45:271078 }
Dan Beamd1cca6e2019-01-03 02:46:271079 if (page >= this.pageDimensions_.length) {
[email protected]312112c72014-04-14 01:45:431080 page = this.pageDimensions_.length - 1;
Dan Beamd1cca6e2019-01-03 02:46:271081 }
[email protected]312112c72014-04-14 01:45:431082
dstockwellafba4e42018-12-02 22:58:061083 const pageDimensions = this.pageDimensions_[page];
[email protected]312112c72014-04-14 01:45:431084
1085 // Compute the page dimensions minus the shadows.
dstockwellafba4e42018-12-02 22:58:061086 const insetDimensions = this.getPageInsetDimensions(page);
[email protected]312112c72014-04-14 01:45:431087
[email protected]345e7c62014-05-02 09:52:581088 // Compute the x-coordinate of the page within the document.
1089 // TODO(raymes): This should really be set when the PDF plugin passes the
1090 // page coordinates, but it isn't yet.
dstockwellafba4e42018-12-02 22:58:061091 const x = (this.documentDimensions_.width - pageDimensions.width) / 2 +
[email protected]345e7c62014-05-02 09:52:581092 Viewport.PAGE_SHADOW.left;
1093 // Compute the space on the left of the document if the document fits
1094 // completely in the screen.
dstockwellafba4e42018-12-02 22:58:061095 let spaceOnLeft =
dbeam70db0fb2017-06-19 17:09:271096 (this.size.width - this.documentDimensions_.width * this.zoom) / 2;
[email protected]345e7c62014-05-02 09:52:581097 spaceOnLeft = Math.max(spaceOnLeft, 0);
[email protected]312112c72014-04-14 01:45:431098
1099 return {
mcnee2999413a2016-12-06 20:29:251100 x: x * this.zoom + spaceOnLeft - this.window_.pageXOffset,
1101 y: insetDimensions.y * this.zoom - this.window_.pageYOffset,
1102 width: insetDimensions.width * this.zoom,
1103 height: insetDimensions.height * this.zoom
[email protected]312112c72014-04-14 01:45:431104 };
Henrique Nakashima50b18e02017-11-21 23:29:571105 },
1106
1107 /**
1108 * Check if the current fitting type is a paged mode.
1109 *
1110 * In a paged mode, page up and page down scroll to the top of the
1111 * previous/next page and part of the page is under the toolbar.
1112 *
1113 * @return {boolean} Whether the current fitting type is a paged mode.
1114 */
1115 isPagedMode: function(page) {
1116 return (
1117 this.fittingType_ == FittingType.FIT_TO_PAGE ||
1118 this.fittingType_ == FittingType.FIT_TO_HEIGHT);
Henrique Nakashima585c7af02018-03-27 04:55:211119 },
1120
1121 /**
1122 * Scroll the viewport to the specified position.
1123 *
1124 * @param {!PartialPoint} point The position to which to move the viewport.
1125 */
1126 scrollTo: function(point) {
1127 let changed = false;
1128 const newPosition = this.position;
1129 if (point.x !== undefined && point.x != newPosition.x) {
1130 newPosition.x = point.x;
1131 changed = true;
1132 }
1133 if (point.y !== undefined && point.y != newPosition.y) {
1134 newPosition.y = point.y;
1135 changed = true;
1136 }
1137
Dan Beamd1cca6e2019-01-03 02:46:271138 if (changed) {
Henrique Nakashima585c7af02018-03-27 04:55:211139 this.position = newPosition;
Dan Beamd1cca6e2019-01-03 02:46:271140 }
Henrique Nakashima585c7af02018-03-27 04:55:211141 },
1142
1143 /**
1144 * Scroll the viewport by the specified delta.
1145 *
1146 * @param {!Point} delta The delta by which to move the viewport.
1147 */
1148 scrollBy: function(delta) {
1149 const newPosition = this.position;
1150 newPosition.x += delta.x;
1151 newPosition.y += delta.y;
1152 this.scrollTo(newPosition);
[email protected]3528d6302014-02-19 08:13:071153 }
1154};