blob: 795a4dea697f6683fe83c0ca054ac93a06fec362 [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
dpapad17f738f2019-10-22 21:03:025import {assert} from 'chrome://resources/js/assert.m.js';
6import {EventTracker} from 'chrome://resources/js/event_tracker.m.js';
7import {$} from 'chrome://resources/js/util.m.js';
8
Hui Yingst8a3f57f2020-02-20 15:12:529import {FittingType} from './constants.js';
dpapad17f738f2019-10-22 21:03:0210import {InactiveZoomManager, ZoomManager} from './zoom_manager.js';
11
rbpotter84b2076a2019-08-23 01:31:1512/**
13 * @typedef {{
14 * width: number,
15 * height: number,
K Moonaa70882f2019-09-26 21:02:4916 * layoutOptions: (!LayoutOptions|undefined),
rbpotter84b2076a2019-08-23 01:31:1517 * pageDimensions: Array<ViewportRect>,
18 * }}
19 */
20let DocumentDimensions;
21
Hui Yingst73ba19a2020-03-10 19:45:2122/**
23 * @typedef {{
24 * defaultPageOrientation: number,
25 * twoUpViewEnabled: boolean,
26 * }}
27 */
dpapad17f738f2019-10-22 21:03:0228export let LayoutOptions;
K Moonaa70882f2019-09-26 21:02:4929
rbpotter84b2076a2019-08-23 01:31:1530/** @typedef {{x: number, y: number}} */
dpapad17f738f2019-10-22 21:03:0231export let Point;
rbpotter84b2076a2019-08-23 01:31:1532
rbpotterde9429d2019-09-12 00:40:0833/** @typedef {{x: (number|undefined), y: (number|undefined)}} */
dpapad17f738f2019-10-22 21:03:0234export let PartialPoint;
rbpotterde9429d2019-09-12 00:40:0835
rbpotter84b2076a2019-08-23 01:31:1536/** @typedef {{width: number, height: number}} */
37let Size;
38
39/** @typedef {{x: number, y: number, width: number, height: number}} */
40let ViewportRect;
Henrique Nakashima585c7af02018-03-27 04:55:2141
42/**
rbpotter84b2076a2019-08-23 01:31:1543 * @param {!ViewportRect} rect1
44 * @param {!ViewportRect} rect2
45 * @return {number} The area of the intersection of the rects
[email protected]3528d6302014-02-19 08:13:0746 */
Jeremy Chinsen54f601822019-08-15 02:15:3747function getIntersectionArea(rect1, rect2) {
48 const left = Math.max(rect1.x, rect2.x);
49 const top = Math.max(rect1.y, rect2.y);
50 const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width);
51 const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height);
52
53 if (left >= right || top >= bottom) {
54 return 0;
55 }
56
57 return (right - left) * (bottom - top);
[email protected]3528d6302014-02-19 08:13:0758}
59
60/**
rbpotter84b2076a2019-08-23 01:31:1561 * @param {!Point} p1
62 * @param {!Point} p2
63 * @return {!Point} The vector between the two points.
mcnee6e1abbf2016-11-09 18:05:3164 */
65function vectorDelta(p1, p2) {
dbeam70db0fb2017-06-19 17:09:2766 return {x: p2.x - p1.x, y: p2.y - p1.y};
mcnee6e1abbf2016-11-09 18:05:3167}
68
rbpotter84b2076a2019-08-23 01:31:1569/**
70 * @param {!Point} coordinateInFrame
71 * @return {!Point} Coordinate converted to plugin coordinates.
72 */
mcnee6e1abbf2016-11-09 18:05:3173function frameToPluginCoordinate(coordinateInFrame) {
dstockwellafba4e42018-12-02 22:58:0674 const container = $('plugin');
mcnee6e1abbf2016-11-09 18:05:3175 return {
76 x: coordinateInFrame.x - container.getBoundingClientRect().left,
77 y: coordinateInFrame.y - container.getBoundingClientRect().top
78 };
79}
80
dpapad17f738f2019-10-22 21:03:0281export class Viewport {
dstockwella685a702019-01-29 02:21:5482 /**
rbpotter84b2076a2019-08-23 01:31:1583 * @param {!Window} window
84 * @param {!HTMLDivElement} sizer The element which represents the size of the
dstockwella685a702019-01-29 02:21:5485 * document in the viewport
rbpotter84b2076a2019-08-23 01:31:1586 * @param {number} scrollbarWidth The width of scrollbars on the page
dstockwella685a702019-01-29 02:21:5487 * @param {number} defaultZoom The default zoom level.
88 * @param {number} topToolbarHeight The number of pixels that should initially
89 * be left blank above the document for the toolbar.
90 */
rbpotter84b2076a2019-08-23 01:31:1591 constructor(window, sizer, scrollbarWidth, defaultZoom, topToolbarHeight) {
92 /** @private {!Window} */
dstockwella685a702019-01-29 02:21:5493 this.window_ = window;
rbpotter84b2076a2019-08-23 01:31:1594
95 /** @private {!HTMLDivElement} */
dstockwella685a702019-01-29 02:21:5496 this.sizer_ = sizer;
rbpotter84b2076a2019-08-23 01:31:1597
98 /** @private {number} */
99 this.scrollbarWidth_ = scrollbarWidth;
100
101 /** @private {number} */
102 this.defaultZoom_ = defaultZoom;
103
104 /** @private {number} */
105 this.topToolbarHeight_ = topToolbarHeight;
106
107 /** @private {function():void} */
108 this.viewportChangedCallback_ = function() {};
109
110 /** @private {function():void} */
111 this.beforeZoomCallback_ = function() {};
112
113 /** @private {function():void} */
114 this.afterZoomCallback_ = function() {};
115
116 /** @private {function(boolean):void} */
117 this.userInitiatedCallback_ = function() {};
118
119 /** @private {boolean} */
dstockwella685a702019-01-29 02:21:54120 this.allowedToChangeZoom_ = false;
rbpotter84b2076a2019-08-23 01:31:15121
122 /** @private {number} */
dstockwella685a702019-01-29 02:21:54123 this.internalZoom_ = 1;
rbpotter84b2076a2019-08-23 01:31:15124
Brian Clifton08b57c02019-12-18 01:29:36125 /**
126 * Predefined zoom factors to be used when zooming in/out. These are in
127 * ascending order.
128 * @private {!Array<number>}
129 */
130 this.presetZoomFactors_ = [];
131
rbpotter84b2076a2019-08-23 01:31:15132 /** @private {?ZoomManager} */
133 this.zoomManager_ = null;
134
dstockwella685a702019-01-29 02:21:54135 /** @private {?DocumentDimensions} */
136 this.documentDimensions_ = null;
rbpotter84b2076a2019-08-23 01:31:15137
dstockwella685a702019-01-29 02:21:54138 /** @private {Array<ViewportRect>} */
139 this.pageDimensions_ = [];
rbpotter84b2076a2019-08-23 01:31:15140
141 /** @private {!FittingType} */
dstockwella685a702019-01-29 02:21:54142 this.fittingType_ = FittingType.NONE;
rbpotter84b2076a2019-08-23 01:31:15143
rbpotter84b2076a2019-08-23 01:31:15144 /** @private {number} */
dstockwella685a702019-01-29 02:21:54145 this.prevScale_ = 1;
rbpotter84b2076a2019-08-23 01:31:15146
147 /** @private {!Viewport.PinchPhase} */
dstockwella685a702019-01-29 02:21:54148 this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
rbpotter84b2076a2019-08-23 01:31:15149
150 /** @private {?Point} */
dstockwella685a702019-01-29 02:21:54151 this.pinchPanVector_ = null;
rbpotter84b2076a2019-08-23 01:31:15152
153 /** @private {?Point} */
dstockwella685a702019-01-29 02:21:54154 this.pinchCenter_ = null;
rbpotter84b2076a2019-08-23 01:31:15155
dstockwella685a702019-01-29 02:21:54156 /** @private {?Point} */
157 this.firstPinchCenterInFrame_ = null;
rbpotter84b2076a2019-08-23 01:31:15158
159 /** @private {number} */
dstockwella685a702019-01-29 02:21:54160 this.rotations_ = 0;
rbpotter84b2076a2019-08-23 01:31:15161
162 /** @private {?Point} */
163 this.oldCenterInContent_ = null;
164
165 /** @private {boolean} */
166 this.keepContentCentered_ = false;
167
168 /** @private {!EventTracker} */
169 this.tracker_ = new EventTracker();
170
171 // Set to a default zoom manager - used in tests.
172 this.setZoomManager(new InactiveZoomManager(this.getZoom.bind(this), 1));
[email protected]3528d6302014-02-19 08:13:07173
dstockwella685a702019-01-29 02:21:54174 window.addEventListener('scroll', this.updateViewport_.bind(this));
175 window.addEventListener('resize', this.resizeWrapper_.bind(this));
176 }
Douglas Stockwell1752f7762018-11-28 23:41:36177
rbpotter84b2076a2019-08-23 01:31:15178 /** @param {function():void} viewportChangedCallback */
179 setViewportChangedCallback(viewportChangedCallback) {
180 this.viewportChangedCallback_ = viewportChangedCallback;
181 }
182
183 /** @param {function():void} beforeZoomCallback */
184 setBeforeZoomCallback(beforeZoomCallback) {
185 this.beforeZoomCallback_ = beforeZoomCallback;
186 }
187
188 /** @param {function():void} afterZoomCallback */
189 setAfterZoomCallback(afterZoomCallback) {
190 this.afterZoomCallback_ = afterZoomCallback;
191 }
192
193 /** @param {function(boolean):void} userInitiatedCallback */
194 setUserInitiatedCallback(userInitiatedCallback) {
195 this.userInitiatedCallback_ = userInitiatedCallback;
196 }
197
Lei Zhangc6c4ec32019-10-31 21:14:28198 rotateClockwise() {
199 this.rotateBySteps_(1);
200 }
201
202 rotateCounterclockwise() {
203 this.rotateBySteps_(3);
204 }
205
Douglas Stockwell1752f7762018-11-28 23:41:36206 /**
rbpotter84b2076a2019-08-23 01:31:15207 * @param {number} n The number of clockwise 90-degree rotations to increment
208 * by.
Douglas Stockwell1752f7762018-11-28 23:41:36209 */
Lei Zhangc6c4ec32019-10-31 21:14:28210 rotateBySteps_(n) {
Douglas Stockwell1752f7762018-11-28 23:41:36211 this.rotations_ = (this.rotations_ + n) % 4;
dstockwella685a702019-01-29 02:21:54212 }
Douglas Stockwell1752f7762018-11-28 23:41:36213
214 /**
rbpotter84b2076a2019-08-23 01:31:15215 * @return {number} The number of clockwise 90-degree rotations that have been
Douglas Stockwell1752f7762018-11-28 23:41:36216 * applied.
217 */
dstockwella685a702019-01-29 02:21:54218 getClockwiseRotations() {
Douglas Stockwell1752f7762018-11-28 23:41:36219 return this.rotations_;
dstockwella685a702019-01-29 02:21:54220 }
Douglas Stockwell1752f7762018-11-28 23:41:36221
Hui Yingst73ba19a2020-03-10 19:45:21222 /** @return {boolean} Whether viewport is in two-up view mode. */
223 twoUpViewEnabled() {
224 const options = this.getLayoutOptions();
225 if (options === undefined) {
226 return false;
227 }
228 return options.twoUpViewEnabled;
Jeremy Chinsenfb6768e2019-08-17 01:09:07229 }
230
231 /**
Brian Clifton08b57c02019-12-18 01:29:36232 * Clamps the zoom factor (or page scale factor) to be within the limits.
233 * @param {number} factor The zoom/scale factor.
234 * @return {number} The factor clamped within the limits.
235 * @private
236 */
237 clampZoom_(factor) {
238 return Math.max(
239 this.presetZoomFactors_[0],
240 Math.min(
241 factor,
242 this.presetZoomFactors_[this.presetZoomFactors_.length - 1]));
243 }
244
245 /**
246 * @param {!Array<number>} factors Array containing zoom/scale factors.
247 */
248 setZoomFactorRange(factors) {
249 assert(factors.length !== 0);
250 this.presetZoomFactors_ = factors;
251 }
252
253 /**
Douglas Stockwell1752f7762018-11-28 23:41:36254 * Converts a page position (e.g. the location of a bookmark) to a screen
255 * position.
Douglas Stockwell1752f7762018-11-28 23:41:36256 * @param {number} page
rbpotter84b2076a2019-08-23 01:31:15257 * @param {!Point} point The position on `page`.
258 * @return {!Point} The screen position.
Douglas Stockwell1752f7762018-11-28 23:41:36259 */
dstockwella685a702019-01-29 02:21:54260 convertPageToScreen(page, point) {
Douglas Stockwell1752f7762018-11-28 23:41:36261 const dimensions = this.getPageInsetDimensions(page);
262
263 // width & height are already rotated.
264 const height = dimensions.height;
265 const width = dimensions.width;
266
267 const matrix = new DOMMatrix();
268
269 const rotation = this.rotations_ * 90;
270 // Set origin for rotation.
rbpotterb43063eb2020-02-20 01:23:10271 if (rotation === 90) {
Douglas Stockwell1752f7762018-11-28 23:41:36272 matrix.translateSelf(width, 0);
rbpotterb43063eb2020-02-20 01:23:10273 } else if (rotation === 180) {
Douglas Stockwell1752f7762018-11-28 23:41:36274 matrix.translateSelf(width, height);
rbpotterb43063eb2020-02-20 01:23:10275 } else if (rotation === 270) {
Douglas Stockwell1752f7762018-11-28 23:41:36276 matrix.translateSelf(0, height);
277 }
278 matrix.rotateSelf(0, 0, rotation);
279
280 // Invert Y position with respect to height as page coordinates are
281 // measured from the bottom left.
282 matrix.translateSelf(0, height);
283 matrix.scaleSelf(1, -1);
284
285 const pointsToPixels = 96 / 72;
286 const result = matrix.transformPoint({
287 x: point.x * pointsToPixels,
288 y: point.y * pointsToPixels,
289 });
290 return {
291 x: result.x + Viewport.PAGE_SHADOW.left,
292 y: result.y + Viewport.PAGE_SHADOW.top,
293 };
dstockwella685a702019-01-29 02:21:54294 }
Douglas Stockwell1752f7762018-11-28 23:41:36295
296
[email protected]3528d6302014-02-19 08:13:07297 /**
raymese6e90c62015-08-10 06:21:40298 * Returns the zoomed and rounded document dimensions for the given zoom.
299 * Rounding is necessary when interacting with the renderer which tends to
300 * operate in integral values (for example for determining if scrollbars
301 * should be shown).
302 * @param {number} zoom The zoom to use to compute the scaled dimensions.
rbpotter84b2076a2019-08-23 01:31:15303 * @return {?Size} Scaled 'width' and 'height' of the document.
raymese6e90c62015-08-10 06:21:40304 * @private
305 */
dstockwella685a702019-01-29 02:21:54306 getZoomedDocumentDimensions_(zoom) {
Dan Beamd1cca6e2019-01-03 02:46:27307 if (!this.documentDimensions_) {
raymese6e90c62015-08-10 06:21:40308 return null;
Dan Beamd1cca6e2019-01-03 02:46:27309 }
raymese6e90c62015-08-10 06:21:40310 return {
311 width: Math.round(this.documentDimensions_.width * zoom),
312 height: Math.round(this.documentDimensions_.height * zoom)
313 };
dstockwella685a702019-01-29 02:21:54314 }
raymese6e90c62015-08-10 06:21:40315
rbpotter84b2076a2019-08-23 01:31:15316 /** @return {!Size} A dictionary with the 'width'/'height' of the document. */
dstockwella685a702019-01-29 02:21:54317 getDocumentDimensions() {
dstockwell118c5362019-01-03 03:01:34318 return {
319 width: this.documentDimensions_.width,
320 height: this.documentDimensions_.height
321 };
dstockwella685a702019-01-29 02:21:54322 }
dstockwell118c5362019-01-03 03:01:34323
324 /**
K Moonaa70882f2019-09-26 21:02:49325 * @return {!LayoutOptions|undefined} A dictionary carrying layout options
326 * from the plugin.
327 */
328 getLayoutOptions() {
329 return this.documentDimensions_ ? this.documentDimensions_.layoutOptions :
330 undefined;
331 }
332
333 /**
rbpotter84b2076a2019-08-23 01:31:15334 * @return {!ViewportRect} ViewportRect for the viewport given current zoom.
Jeremy Chinsenfb6768e2019-08-17 01:09:07335 * @private
336 */
337 getViewportRect_() {
rbpotter84b2076a2019-08-23 01:31:15338 const zoom = this.getZoom();
Jeremy Chinsenfb6768e2019-08-17 01:09:07339 return {
rbpotter84b2076a2019-08-23 01:31:15340 x: this.position.x / zoom,
341 y: this.position.y / zoom,
342 width: this.size.width / zoom,
343 height: this.size.height / zoom
Jeremy Chinsenfb6768e2019-08-17 01:09:07344 };
345 }
346
347 /**
rbpotter84b2076a2019-08-23 01:31:15348 * @param {number} zoom Zoom to compute scrollbars for
349 * @return {{horizontal: boolean, vertical: boolean}} Whether horizontal or
dstockwella685a702019-01-29 02:21:54350 * vertical scrollbars are needed.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42351 * @private
[email protected]3528d6302014-02-19 08:13:07352 */
dstockwella685a702019-01-29 02:21:54353 documentNeedsScrollbars_(zoom) {
dstockwellafba4e42018-12-02 22:58:06354 const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
raymese6e90c62015-08-10 06:21:40355 if (!zoomedDimensions) {
dbeam70db0fb2017-06-19 17:09:27356 return {horizontal: false, vertical: false};
[email protected]499e9562014-06-26 05:45:27357 }
sammc63334fed2014-11-10 03:07:35358
359 // If scrollbars are required for one direction, expand the document in the
360 // other direction to take the width of the scrollbars into account when
361 // deciding whether the other direction needs scrollbars.
Dan Beamd1cca6e2019-01-03 02:46:27362 if (zoomedDimensions.width > this.window_.innerWidth) {
raymese6e90c62015-08-10 06:21:40363 zoomedDimensions.height += this.scrollbarWidth_;
Dan Beamd1cca6e2019-01-03 02:46:27364 } else if (zoomedDimensions.height > this.window_.innerHeight) {
raymese6e90c62015-08-10 06:21:40365 zoomedDimensions.width += this.scrollbarWidth_;
Dan Beamd1cca6e2019-01-03 02:46:27366 }
[email protected]3528d6302014-02-19 08:13:07367 return {
raymese6e90c62015-08-10 06:21:40368 horizontal: zoomedDimensions.width > this.window_.innerWidth,
raymes051fb2c2015-09-21 04:56:41369 vertical: zoomedDimensions.height + this.topToolbarHeight_ >
370 this.window_.innerHeight
[email protected]3528d6302014-02-19 08:13:07371 };
dstockwella685a702019-01-29 02:21:54372 }
[email protected]3528d6302014-02-19 08:13:07373
374 /**
rbpotter84b2076a2019-08-23 01:31:15375 * @return {!{horizontal: boolean, vertical: boolean}} Whether horizontal and
376 * vertical scrollbars are needed.
[email protected]3528d6302014-02-19 08:13:07377 */
dstockwella685a702019-01-29 02:21:54378 documentHasScrollbars() {
rbpotter84b2076a2019-08-23 01:31:15379 return this.documentNeedsScrollbars_(this.getZoom());
dstockwella685a702019-01-29 02:21:54380 }
[email protected]3528d6302014-02-19 08:13:07381
382 /**
rbpotter84b2076a2019-08-23 01:31:15383 * Helper function called when the zoomed document size changes. Updates the
384 * sizer's width and height.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42385 * @private
[email protected]3528d6302014-02-19 08:13:07386 */
dstockwella685a702019-01-29 02:21:54387 contentSizeChanged_() {
rbpotter84b2076a2019-08-23 01:31:15388 const zoomedDimensions = this.getZoomedDocumentDimensions_(this.getZoom());
raymese6e90c62015-08-10 06:21:40389 if (zoomedDimensions) {
390 this.sizer_.style.width = zoomedDimensions.width + 'px';
dbeam70db0fb2017-06-19 17:09:27391 this.sizer_.style.height =
392 zoomedDimensions.height + this.topToolbarHeight_ + 'px';
[email protected]345e7c62014-05-02 09:52:58393 }
dstockwella685a702019-01-29 02:21:54394 }
[email protected]3528d6302014-02-19 08:13:07395
396 /**
[email protected]312112c72014-04-14 01:45:43397 * Called when the viewport should be updated.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42398 * @private
[email protected]3528d6302014-02-19 08:13:07399 */
dstockwella685a702019-01-29 02:21:54400 updateViewport_() {
[email protected]312112c72014-04-14 01:45:43401 this.viewportChangedCallback_();
dstockwella685a702019-01-29 02:21:54402 }
[email protected]312112c72014-04-14 01:45:43403
404 /**
rbpottera82dacc2017-09-08 19:39:39405 * Called when the browser window size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42406 * @private
rbpottera82dacc2017-09-08 19:39:39407 */
dstockwella685a702019-01-29 02:21:54408 resizeWrapper_() {
rbpotter84b2076a2019-08-23 01:31:15409 this.userInitiatedCallback_(false);
rbpottera82dacc2017-09-08 19:39:39410 this.resize_();
rbpotter84b2076a2019-08-23 01:31:15411 this.userInitiatedCallback_(true);
dstockwella685a702019-01-29 02:21:54412 }
rbpottera82dacc2017-09-08 19:39:39413
414 /**
[email protected]312112c72014-04-14 01:45:43415 * Called when the viewport size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42416 * @private
[email protected]312112c72014-04-14 01:45:43417 */
dstockwella685a702019-01-29 02:21:54418 resize_() {
rbpotterb43063eb2020-02-20 01:23:10419 if (this.fittingType_ === FittingType.FIT_TO_PAGE) {
raymes161514f2015-02-17 05:09:51420 this.fitToPageInternal_(false);
rbpotterb43063eb2020-02-20 01:23:10421 } else if (this.fittingType_ === FittingType.FIT_TO_WIDTH) {
[email protected]312112c72014-04-14 01:45:43422 this.fitToWidth();
rbpotterb43063eb2020-02-20 01:23:10423 } else if (this.fittingType_ === FittingType.FIT_TO_HEIGHT) {
Henrique Nakashima50b18e02017-11-21 23:29:57424 this.fitToHeightInternal_(false);
rbpotterb43063eb2020-02-20 01:23:10425 } else if (this.internalZoom_ === 0) {
Henrique Nakashima4cf9ed42018-09-07 21:02:03426 this.fitToNone();
Dan Beamd1cca6e2019-01-03 02:46:27427 } else {
[email protected]312112c72014-04-14 01:45:43428 this.updateViewport_();
Dan Beamd1cca6e2019-01-03 02:46:27429 }
dstockwella685a702019-01-29 02:21:54430 }
[email protected]312112c72014-04-14 01:45:43431
rbpotter84b2076a2019-08-23 01:31:15432 /** @return {!Point} The scroll position of the viewport. */
[email protected]312112c72014-04-14 01:45:43433 get position() {
434 return {
435 x: this.window_.pageXOffset,
raymesbd82a942015-08-05 07:05:18436 y: this.window_.pageYOffset - this.topToolbarHeight_
[email protected]312112c72014-04-14 01:45:43437 };
dstockwella685a702019-01-29 02:21:54438 }
[email protected]312112c72014-04-14 01:45:43439
440 /**
441 * Scroll the viewport to the specified position.
rbpotter84b2076a2019-08-23 01:31:15442 * @param {!Point} position The position to scroll to.
[email protected]312112c72014-04-14 01:45:43443 */
444 set position(position) {
raymesbd82a942015-08-05 07:05:18445 this.window_.scrollTo(position.x, position.y + this.topToolbarHeight_);
dstockwella685a702019-01-29 02:21:54446 }
[email protected]312112c72014-04-14 01:45:43447
rbpotter84b2076a2019-08-23 01:31:15448 /** @return {!Size} the size of the viewport excluding scrollbars. */
[email protected]312112c72014-04-14 01:45:43449 get size() {
rbpotter84b2076a2019-08-23 01:31:15450 const needsScrollbars = this.documentNeedsScrollbars_(this.getZoom());
dstockwellafba4e42018-12-02 22:58:06451 const scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0;
452 const scrollbarHeight =
453 needsScrollbars.horizontal ? this.scrollbarWidth_ : 0;
[email protected]312112c72014-04-14 01:45:43454 return {
455 width: this.window_.innerWidth - scrollbarWidth,
456 height: this.window_.innerHeight - scrollbarHeight
457 };
dstockwella685a702019-01-29 02:21:54458 }
[email protected]312112c72014-04-14 01:45:43459
rbpotter84b2076a2019-08-23 01:31:15460 /** @return {number} The current zoom. */
461 getZoom() {
mcnee2999413a2016-12-06 20:29:25462 return this.zoomManager_.applyBrowserZoom(this.internalZoom_);
dstockwella685a702019-01-29 02:21:54463 }
mcnee2999413a2016-12-06 20:29:25464
rbpotter84b2076a2019-08-23 01:31:15465 /** @param {!ZoomManager} manager */
466 setZoomManager(manager) {
467 this.resetTracker();
mcnee2999413a2016-12-06 20:29:25468 this.zoomManager_ = manager;
rbpotter84b2076a2019-08-23 01:31:15469 this.tracker_.add(
470 this.zoomManager_.getEventTarget(), 'set-zoom',
471 e => this.setZoom(e.detail));
472 this.tracker_.add(
473 this.zoomManager_.getEventTarget(), 'update-zoom-from-browser',
474 this.updateZoomFromBrowserChange_.bind(this));
dstockwella685a702019-01-29 02:21:54475 }
[email protected]312112c72014-04-14 01:45:43476
477 /**
rbpotter84b2076a2019-08-23 01:31:15478 * @return {!Viewport.PinchPhase} The phase of the current pinch gesture for
mcnee6e1abbf2016-11-09 18:05:31479 * the viewport.
480 */
481 get pinchPhase() {
482 return this.pinchPhase_;
dstockwella685a702019-01-29 02:21:54483 }
mcnee6e1abbf2016-11-09 18:05:31484
485 /**
rbpotter84b2076a2019-08-23 01:31:15486 * @return {?Point} The panning caused by the current pinch gesture (as
mcnee6e1abbf2016-11-09 18:05:31487 * the deltas of the x and y coordinates).
488 */
489 get pinchPanVector() {
490 return this.pinchPanVector_;
dstockwella685a702019-01-29 02:21:54491 }
mcnee6e1abbf2016-11-09 18:05:31492
493 /**
rbpotter84b2076a2019-08-23 01:31:15494 * @return {?Point} The coordinates of the center of the current pinch
dstockwella685a702019-01-29 02:21:54495 * gesture.
mcnee6e1abbf2016-11-09 18:05:31496 */
497 get pinchCenter() {
498 return this.pinchCenter_;
dstockwella685a702019-01-29 02:21:54499 }
mcnee6e1abbf2016-11-09 18:05:31500
501 /**
[email protected]499e9562014-06-26 05:45:27502 * Used to wrap a function that might perform zooming on the viewport. This is
503 * required so that we can notify the plugin that zooming is in progress
504 * so that while zooming is taking place it can stop reacting to scroll events
505 * from the viewport. This is to avoid flickering.
rbpotter84b2076a2019-08-23 01:31:15506 * @param {function():void} f Function to wrap
Henrique Nakashimad5de6d0d2018-04-13 18:02:42507 * @private
[email protected]499e9562014-06-26 05:45:27508 */
dstockwella685a702019-01-29 02:21:54509 mightZoom_(f) {
[email protected]499e9562014-06-26 05:45:27510 this.beforeZoomCallback_();
511 this.allowedToChangeZoom_ = true;
512 f();
513 this.allowedToChangeZoom_ = false;
514 this.afterZoomCallback_();
rbpotter84b2076a2019-08-23 01:31:15515 this.zoomManager_.onPdfZoomChange();
dstockwella685a702019-01-29 02:21:54516 }
[email protected]499e9562014-06-26 05:45:27517
518 /**
rbpotter84b2076a2019-08-23 01:31:15519 * @param {number} newZoom The zoom level to set.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42520 * @private
[email protected]312112c72014-04-14 01:45:43521 */
dstockwella685a702019-01-29 02:21:54522 setZoomInternal_(newZoom) {
dstockwell154f9c42019-01-01 23:36:34523 assert(
524 this.allowedToChangeZoom_,
525 'Called Viewport.setZoomInternal_ without calling ' +
526 'Viewport.mightZoom_.');
sammc5c33b7e2014-11-07 04:03:09527 // Record the scroll position (relative to the top-left of the window).
rbpotter84b2076a2019-08-23 01:31:15528 let zoom = this.getZoom();
dstockwellafba4e42018-12-02 22:58:06529 const currentScrollPos = {
rbpotter84b2076a2019-08-23 01:31:15530 x: this.position.x / zoom,
531 y: this.position.y / zoom
raymesbd82a942015-08-05 07:05:18532 };
Henrique Nakashima4cf9ed42018-09-07 21:02:03533
mcnee2999413a2016-12-06 20:29:25534 this.internalZoom_ = newZoom;
[email protected]3528d6302014-02-19 08:13:07535 this.contentSizeChanged_();
536 // Scroll to the scaled scroll position.
rbpotter84b2076a2019-08-23 01:31:15537 zoom = this.getZoom();
raymesbd82a942015-08-05 07:05:18538 this.position = {
rbpotter84b2076a2019-08-23 01:31:15539 x: currentScrollPos.x * zoom,
540 y: currentScrollPos.y * zoom
raymesbd82a942015-08-05 07:05:18541 };
dstockwella685a702019-01-29 02:21:54542 }
[email protected]3528d6302014-02-19 08:13:07543
544 /**
mcnee6e1abbf2016-11-09 18:05:31545 * Sets the zoom of the viewport.
546 * Same as setZoomInternal_ but for pinch zoom we have some more operations.
547 * @param {number} scaleDelta The zoom delta.
rbpotter84b2076a2019-08-23 01:31:15548 * @param {!Point} center The pinch center in content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42549 * @private
mcnee6e1abbf2016-11-09 18:05:31550 */
dstockwella685a702019-01-29 02:21:54551 setPinchZoomInternal_(scaleDelta, center) {
dbeam70db0fb2017-06-19 17:09:27552 assert(
553 this.allowedToChangeZoom_,
mcnee6e1abbf2016-11-09 18:05:31554 'Called Viewport.setPinchZoomInternal_ without calling ' +
dbeam70db0fb2017-06-19 17:09:27555 'Viewport.mightZoom_.');
Brian Clifton08b57c02019-12-18 01:29:36556 this.internalZoom_ = this.clampZoom_(this.internalZoom_ * scaleDelta);
mcnee6e1abbf2016-11-09 18:05:31557
rbpotter84b2076a2019-08-23 01:31:15558 const newCenterInContent = this.frameToContent_(center);
dstockwellafba4e42018-12-02 22:58:06559 const delta = {
rbpotter84b2076a2019-08-23 01:31:15560 x: (newCenterInContent.x - this.oldCenterInContent_.x),
561 y: (newCenterInContent.y - this.oldCenterInContent_.y)
mcnee6e1abbf2016-11-09 18:05:31562 };
563
564 // Record the scroll position (relative to the pinch center).
rbpotter84b2076a2019-08-23 01:31:15565 const zoom = this.getZoom();
dstockwellafba4e42018-12-02 22:58:06566 const currentScrollPos = {
rbpotter84b2076a2019-08-23 01:31:15567 x: this.position.x - delta.x * zoom,
568 y: this.position.y - delta.y * zoom
mcnee6e1abbf2016-11-09 18:05:31569 };
570
571 this.contentSizeChanged_();
572 // Scroll to the scaled scroll position.
dbeam70db0fb2017-06-19 17:09:27573 this.position = {x: currentScrollPos.x, y: currentScrollPos.y};
dstockwella685a702019-01-29 02:21:54574 }
mcnee6e1abbf2016-11-09 18:05:31575
576 /**
mcnee6e1abbf2016-11-09 18:05:31577 * Converts a point from frame to content coordinates.
rbpotter84b2076a2019-08-23 01:31:15578 * @param {!Point} framePoint The frame coordinates.
579 * @return {!Point} The content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42580 * @private
mcnee6e1abbf2016-11-09 18:05:31581 */
rbpotter84b2076a2019-08-23 01:31:15582 frameToContent_(framePoint) {
mcnee6e1abbf2016-11-09 18:05:31583 // TODO(mcnee) Add a helper Point class to avoid duplicating operations
584 // on plain {x,y} objects.
rbpotter84b2076a2019-08-23 01:31:15585 const zoom = this.getZoom();
mcnee6e1abbf2016-11-09 18:05:31586 return {
rbpotter84b2076a2019-08-23 01:31:15587 x: (framePoint.x + this.position.x) / zoom,
588 y: (framePoint.y + this.position.y) / zoom
mcnee6e1abbf2016-11-09 18:05:31589 };
dstockwella685a702019-01-29 02:21:54590 }
mcnee6e1abbf2016-11-09 18:05:31591
rbpotter4b8484f2020-05-22 04:52:26592 /** @param {number} newZoom The zoom level to zoom to. */
dstockwella685a702019-01-29 02:21:54593 setZoom(newZoom) {
Henrique Nakashima50b18e02017-11-21 23:29:57594 this.fittingType_ = FittingType.NONE;
dpapad9afc2802017-08-09 22:01:43595 this.mightZoom_(() => {
Brian Clifton08b57c02019-12-18 01:29:36596 this.setZoomInternal_(this.clampZoom_(newZoom));
[email protected]fbad5bb2014-07-18 07:20:36597 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43598 });
dstockwella685a702019-01-29 02:21:54599 }
[email protected]499e9562014-06-26 05:45:27600
rbpotter84b2076a2019-08-23 01:31:15601 /**
602 * @param {!CustomEvent<number>} e Event containing the old browser zoom.
603 * @private
604 */
605 updateZoomFromBrowserChange_(e) {
606 const oldBrowserZoom = e.detail;
dpapad9afc2802017-08-09 22:01:43607 this.mightZoom_(() => {
mcnee2999413a2016-12-06 20:29:25608 // Record the scroll position (relative to the top-left of the window).
dstockwellafba4e42018-12-02 22:58:06609 const oldZoom = oldBrowserZoom * this.internalZoom_;
610 const currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25611 x: this.position.x / oldZoom,
612 y: this.position.y / oldZoom
613 };
614 this.contentSizeChanged_();
rbpotter84b2076a2019-08-23 01:31:15615 const newZoom = this.getZoom();
mcnee2999413a2016-12-06 20:29:25616 // Scroll to the scaled scroll position.
617 this.position = {
rbpotter84b2076a2019-08-23 01:31:15618 x: currentScrollPos.x * newZoom,
619 y: currentScrollPos.y * newZoom
mcnee2999413a2016-12-06 20:29:25620 };
621 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43622 });
dstockwella685a702019-01-29 02:21:54623 }
mcnee2999413a2016-12-06 20:29:25624
rbpotter84b2076a2019-08-23 01:31:15625 /** @return {number} The width of scrollbars in the viewport in pixels. */
[email protected]312112c72014-04-14 01:45:43626 get scrollbarWidth() {
627 return this.scrollbarWidth_;
dstockwella685a702019-01-29 02:21:54628 }
[email protected]3528d6302014-02-19 08:13:07629
rbpotter84b2076a2019-08-23 01:31:15630 /** @return {FittingType} The fitting type the viewport is currently in. */
[email protected]312112c72014-04-14 01:45:43631 get fittingType() {
632 return this.fittingType_;
dstockwella685a702019-01-29 02:21:54633 }
[email protected]3528d6302014-02-19 08:13:07634
635 /**
Jeremy Chinsen54f601822019-08-15 02:15:37636 * Get the page at a given y position. If there are multiple pages
637 * overlapping the given y-coordinate, return the page with the smallest
638 * index.
rbpotter84b2076a2019-08-23 01:31:15639 * @param {number} y The y-coordinate to get the page at.
640 * @return {number} The index of a page overlapping the given y-coordinate.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42641 * @private
[email protected]56b7e3c2014-02-20 04:31:24642 */
dstockwella685a702019-01-29 02:21:54643 getPageAtY_(y) {
dstockwellafba4e42018-12-02 22:58:06644 let min = 0;
645 let max = this.pageDimensions_.length - 1;
[email protected]56b7e3c2014-02-20 04:31:24646 while (max >= min) {
dstockwellafba4e42018-12-02 22:58:06647 const page = Math.floor(min + ((max - min) / 2));
[email protected]13df2a42014-02-27 03:50:41648 // There might be a gap between the pages, in which case use the bottom
649 // of the previous page as the top for finding the page.
dstockwellafba4e42018-12-02 22:58:06650 let top = 0;
[email protected]13df2a42014-02-27 03:50:41651 if (page > 0) {
652 top = this.pageDimensions_[page - 1].y +
653 this.pageDimensions_[page - 1].height;
654 }
dstockwellafba4e42018-12-02 22:58:06655 const bottom =
dbeam70db0fb2017-06-19 17:09:27656 this.pageDimensions_[page].y + this.pageDimensions_[page].height;
[email protected]13df2a42014-02-27 03:50:41657
rbpotter0a1022f2019-10-10 18:13:50658 if (top <= y && y <= bottom) {
[email protected]56b7e3c2014-02-20 04:31:24659 return page;
Dan Beamd1cca6e2019-01-03 02:46:27660 }
Lei Zhangddc0ef032017-11-22 02:14:24661
Dan Beamd1cca6e2019-01-03 02:46:27662 if (top > y) {
[email protected]56b7e3c2014-02-20 04:31:24663 max = page - 1;
Dan Beamd1cca6e2019-01-03 02:46:27664 } else {
[email protected]56b7e3c2014-02-20 04:31:24665 min = page + 1;
Dan Beamd1cca6e2019-01-03 02:46:27666 }
[email protected]56b7e3c2014-02-20 04:31:24667 }
668 return 0;
dstockwella685a702019-01-29 02:21:54669 }
[email protected]56b7e3c2014-02-20 04:31:24670
Jeremy Chinsen54f601822019-08-15 02:15:37671 /**
Jeremy Chinsenfb6768e2019-08-17 01:09:07672 * Return the last page visible in the viewport. Returns the last index of the
673 * document if the viewport is below the document.
rbpotter84b2076a2019-08-23 01:31:15674 * @param {!ViewportRect} viewportRect
675 * @return {number} The highest index of the pages visible in the viewport.
Jeremy Chinsen54f601822019-08-15 02:15:37676 * @private
677 */
rbpotter84b2076a2019-08-23 01:31:15678 getLastPageInViewport_(viewportRect) {
Jeremy Chinsenfb6768e2019-08-17 01:09:07679 const pageAtY = this.getPageAtY_(viewportRect.y + viewportRect.height);
Jeremy Chinsen54f601822019-08-15 02:15:37680
Hui Yingst73ba19a2020-03-10 19:45:21681 if (!this.twoUpViewEnabled() || pageAtY % 2 === 1 ||
Jeremy Chinsenfb6768e2019-08-17 01:09:07682 pageAtY + 1 >= this.pageDimensions_.length) {
683 return pageAtY;
Jeremy Chinsen54f601822019-08-15 02:15:37684 }
685
Jeremy Chinsenfb6768e2019-08-17 01:09:07686 const nextPage = this.pageDimensions_[pageAtY + 1];
687 return getIntersectionArea(viewportRect, nextPage) > 0 ? pageAtY + 1 :
688 pageAtY;
Jeremy Chinsen54f601822019-08-15 02:15:37689 }
690
rbpotter84b2076a2019-08-23 01:31:15691 /**
692 * @param {!Point} point
693 * @return {boolean} Whether |point| (in screen coordinates) is inside a page
694 */
dstockwell09815ba2019-01-16 06:44:24695 isPointInsidePage(point) {
rbpotter84b2076a2019-08-23 01:31:15696 const zoom = this.getZoom();
dstockwell09815ba2019-01-16 06:44:24697 const size = this.size;
698 const position = this.position;
699 const page = this.getPageAtY_((position.y + point.y) / zoom);
700 const pageWidth = this.pageDimensions_[page].width * zoom;
701 const documentWidth = this.getDocumentDimensions().width * zoom;
702
703 const outerWidth = Math.max(size.width, documentWidth);
704
705 if (pageWidth >= outerWidth) {
706 return true;
707 }
708
709 const x = point.x + position.x;
710
711 const minX = (outerWidth - pageWidth) / 2;
712 const maxX = outerWidth - minX;
713 return x >= minX && x <= maxX;
dstockwella685a702019-01-29 02:21:54714 }
dstockwell09815ba2019-01-16 06:44:24715
716 /**
rbpotter84b2076a2019-08-23 01:31:15717 * @return {number} The index of the page with the greatest proportion of its
718 * area in the current viewport.
[email protected]3528d6302014-02-19 08:13:07719 */
dstockwella685a702019-01-29 02:21:54720 getMostVisiblePage() {
Jeremy Chinsenfb6768e2019-08-17 01:09:07721 const viewportRect = this.getViewportRect_();
Jeremy Chinsen54f601822019-08-15 02:15:37722
723 const firstVisiblePage = this.getPageAtY_(viewportRect.y);
rbpotter84b2076a2019-08-23 01:31:15724 const lastPossibleVisiblePage = this.getLastPageInViewport_(viewportRect);
Jeremy Chinsen54f601822019-08-15 02:15:37725 if (firstVisiblePage === lastPossibleVisiblePage) {
726 return firstVisiblePage;
Dan Beamd1cca6e2019-01-03 02:46:27727 }
Jeremy Chinsen54f601822019-08-15 02:15:37728
729 let mostVisiblePage = firstVisiblePage;
730 let largestIntersection = 0;
731
732 for (let i = firstVisiblePage; i < lastPossibleVisiblePage + 1; i++) {
733 const pageArea =
734 this.pageDimensions_[i].width * this.pageDimensions_[i].height;
735
736 // TODO(thestig): check whether we can remove this check.
737 if (pageArea <= 0) {
738 continue;
739 }
740
741 const pageIntersectionArea =
742 getIntersectionArea(this.pageDimensions_[i], viewportRect) / pageArea;
743
744 if (pageIntersectionArea > largestIntersection) {
745 mostVisiblePage = i;
746 largestIntersection = pageIntersectionArea;
747 }
748 }
749
750 return mostVisiblePage;
dstockwella685a702019-01-29 02:21:54751 }
[email protected]3528d6302014-02-19 08:13:07752
753 /**
Henrique Nakashima50b18e02017-11-21 23:29:57754 * Compute the zoom level for fit-to-page, fit-to-width or fit-to-height.
Henrique Nakashima50b18e02017-11-21 23:29:57755 * At least one of {fitWidth, fitHeight} must be true.
rbpotter84b2076a2019-08-23 01:31:15756 * @param {!Size} pageDimensions The dimensions of a given page in px.
757 * @param {boolean} fitWidth Whether the whole width of the page needs to be
758 * in the viewport.
759 * @param {boolean} fitHeight Whether the whole height of the page needs to be
760 * in the viewport.
761 * @return {number} The internal zoom to set
Henrique Nakashimad5de6d0d2018-04-13 18:02:42762 * @private
[email protected]3528d6302014-02-19 08:13:07763 */
dstockwella685a702019-01-29 02:21:54764 computeFittingZoom_(pageDimensions, fitWidth, fitHeight) {
Henrique Nakashima50b18e02017-11-21 23:29:57765 assert(
766 fitWidth || fitHeight,
767 'Invalid parameters. At least one of fitWidth and fitHeight must be ' +
768 'true.');
769
[email protected]3528d6302014-02-19 08:13:07770 // First compute the zoom without scrollbars.
dstockwellafba4e42018-12-02 22:58:06771 let zoom = this.computeFittingZoomGivenDimensions_(
Henrique Nakashima50b18e02017-11-21 23:29:57772 fitWidth, fitHeight, this.window_.innerWidth, this.window_.innerHeight,
773 pageDimensions.width, pageDimensions.height);
774
[email protected]3528d6302014-02-19 08:13:07775 // Check if there needs to be any scrollbars.
dstockwellafba4e42018-12-02 22:58:06776 const needsScrollbars = this.documentNeedsScrollbars_(zoom);
[email protected]3528d6302014-02-19 08:13:07777
778 // If the document fits, just return the zoom.
Dan Beamd1cca6e2019-01-03 02:46:27779 if (!needsScrollbars.horizontal && !needsScrollbars.vertical) {
[email protected]3528d6302014-02-19 08:13:07780 return zoom;
Dan Beamd1cca6e2019-01-03 02:46:27781 }
[email protected]3528d6302014-02-19 08:13:07782
dstockwellafba4e42018-12-02 22:58:06783 const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
[email protected]3528d6302014-02-19 08:13:07784
785 // Check if adding a scrollbar will result in needing the other scrollbar.
dstockwellafba4e42018-12-02 22:58:06786 const scrollbarWidth = this.scrollbarWidth_;
[email protected]312112c72014-04-14 01:45:43787 if (needsScrollbars.horizontal &&
[email protected]3528d6302014-02-19 08:13:07788 zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43789 needsScrollbars.vertical = true;
[email protected]3528d6302014-02-19 08:13:07790 }
[email protected]312112c72014-04-14 01:45:43791 if (needsScrollbars.vertical &&
[email protected]3528d6302014-02-19 08:13:07792 zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43793 needsScrollbars.horizontal = true;
[email protected]3528d6302014-02-19 08:13:07794 }
795
796 // Compute available window space.
dstockwellafba4e42018-12-02 22:58:06797 const windowWithScrollbars = {
[email protected]3528d6302014-02-19 08:13:07798 width: this.window_.innerWidth,
799 height: this.window_.innerHeight
800 };
Dan Beamd1cca6e2019-01-03 02:46:27801 if (needsScrollbars.horizontal) {
[email protected]3528d6302014-02-19 08:13:07802 windowWithScrollbars.height -= scrollbarWidth;
Dan Beamd1cca6e2019-01-03 02:46:27803 }
804 if (needsScrollbars.vertical) {
[email protected]3528d6302014-02-19 08:13:07805 windowWithScrollbars.width -= scrollbarWidth;
Dan Beamd1cca6e2019-01-03 02:46:27806 }
[email protected]3528d6302014-02-19 08:13:07807
808 // Recompute the zoom.
Henrique Nakashima50b18e02017-11-21 23:29:57809 zoom = this.computeFittingZoomGivenDimensions_(
810 fitWidth, fitHeight, windowWithScrollbars.width,
811 windowWithScrollbars.height, pageDimensions.width,
812 pageDimensions.height);
813
mcnee2999413a2016-12-06 20:29:25814 return this.zoomManager_.internalZoomComponent(zoom);
dstockwella685a702019-01-29 02:21:54815 }
[email protected]3528d6302014-02-19 08:13:07816
817 /**
Henrique Nakashima50b18e02017-11-21 23:29:57818 * Compute a zoom level given the dimensions to fit and the actual numbers
819 * in those dimensions.
rbpotter84b2076a2019-08-23 01:31:15820 * @param {boolean} fitWidth Whether to constrain the page width to the
821 * window.
822 * @param {boolean} fitHeight Whether to constrain the page height to the
823 * window.
824 * @param {number} windowWidth Width of the window in px.
825 * @param {number} windowHeight Height of the window in px.
826 * @param {number} pageWidth Width of the page in px.
827 * @param {number} pageHeight Height of the page in px.
828 * @return {number} The internal zoom to set
Henrique Nakashimad5de6d0d2018-04-13 18:02:42829 * @private
Henrique Nakashima50b18e02017-11-21 23:29:57830 */
dstockwella685a702019-01-29 02:21:54831 computeFittingZoomGivenDimensions_(
Henrique Nakashima50b18e02017-11-21 23:29:57832 fitWidth, fitHeight, windowWidth, windowHeight, pageWidth, pageHeight) {
833 // Assumes at least one of {fitWidth, fitHeight} is set.
dstockwellafba4e42018-12-02 22:58:06834 let zoomWidth;
835 let zoomHeight;
Henrique Nakashima50b18e02017-11-21 23:29:57836
Dan Beamd1cca6e2019-01-03 02:46:27837 if (fitWidth) {
Henrique Nakashima50b18e02017-11-21 23:29:57838 zoomWidth = windowWidth / pageWidth;
Dan Beamd1cca6e2019-01-03 02:46:27839 }
Henrique Nakashima50b18e02017-11-21 23:29:57840
Dan Beamd1cca6e2019-01-03 02:46:27841 if (fitHeight) {
Henrique Nakashima50b18e02017-11-21 23:29:57842 zoomHeight = windowHeight / pageHeight;
Dan Beamd1cca6e2019-01-03 02:46:27843 }
Henrique Nakashima50b18e02017-11-21 23:29:57844
dstockwellafba4e42018-12-02 22:58:06845 let zoom;
Henrique Nakashima4cf9ed42018-09-07 21:02:03846 if (!fitWidth && fitHeight) {
847 zoom = zoomHeight;
848 } else if (fitWidth && !fitHeight) {
849 zoom = zoomWidth;
850 } else {
851 // Assume fitWidth && fitHeight
852 zoom = Math.min(zoomWidth, zoomHeight);
853 }
Henrique Nakashima50b18e02017-11-21 23:29:57854
Henrique Nakashima4cf9ed42018-09-07 21:02:03855 return Math.max(zoom, 0);
dstockwella685a702019-01-29 02:21:54856 }
Henrique Nakashima50b18e02017-11-21 23:29:57857
rbpotter84b2076a2019-08-23 01:31:15858 /** Zoom the viewport so that the page width consumes the entire viewport. */
dstockwella685a702019-01-29 02:21:54859 fitToWidth() {
dpapad9afc2802017-08-09 22:01:43860 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57861 this.fittingType_ = FittingType.FIT_TO_WIDTH;
Dan Beamd1cca6e2019-01-03 02:46:27862 if (!this.documentDimensions_) {
[email protected]499e9562014-06-26 05:45:27863 return;
Dan Beamd1cca6e2019-01-03 02:46:27864 }
[email protected]499e9562014-06-26 05:45:27865 // When computing fit-to-width, the maximum width of a page in the
866 // document is used, which is equal to the size of the document width.
dbeam70db0fb2017-06-19 17:09:27867 this.setZoomInternal_(
Henrique Nakashima50b18e02017-11-21 23:29:57868 this.computeFittingZoom_(this.documentDimensions_, true, false));
[email protected]499e9562014-06-26 05:45:27869 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43870 });
dstockwella685a702019-01-29 02:21:54871 }
[email protected]3528d6302014-02-19 08:13:07872
873 /**
Henrique Nakashima50b18e02017-11-21 23:29:57874 * Zoom the viewport so that the page height consumes the entire viewport.
875 * @param {boolean} scrollToTopOfPage Set to true if the viewport should be
876 * scrolled to the top of the current page. Set to false if the viewport
877 * should remain at the current scroll position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42878 * @private
Henrique Nakashima50b18e02017-11-21 23:29:57879 */
dstockwella685a702019-01-29 02:21:54880 fitToHeightInternal_(scrollToTopOfPage) {
Henrique Nakashima50b18e02017-11-21 23:29:57881 this.mightZoom_(() => {
882 this.fittingType_ = FittingType.FIT_TO_HEIGHT;
Dan Beamd1cca6e2019-01-03 02:46:27883 if (!this.documentDimensions_) {
Henrique Nakashima50b18e02017-11-21 23:29:57884 return;
Dan Beamd1cca6e2019-01-03 02:46:27885 }
dstockwellafba4e42018-12-02 22:58:06886 const page = this.getMostVisiblePage();
Henrique Nakashima50b18e02017-11-21 23:29:57887 // When computing fit-to-height, the maximum height of the current page
888 // is used.
dstockwellafba4e42018-12-02 22:58:06889 const dimensions = {
Henrique Nakashima50b18e02017-11-21 23:29:57890 width: 0,
891 height: this.pageDimensions_[page].height,
892 };
893 this.setZoomInternal_(this.computeFittingZoom_(dimensions, false, true));
894 if (scrollToTopOfPage) {
rbpotter84b2076a2019-08-23 01:31:15895 this.position = {
896 x: 0,
897 y: this.pageDimensions_[page].y * this.getZoom()
898 };
Henrique Nakashima50b18e02017-11-21 23:29:57899 }
900 this.updateViewport_();
901 });
dstockwella685a702019-01-29 02:21:54902 }
Henrique Nakashima50b18e02017-11-21 23:29:57903
rbpotter84b2076a2019-08-23 01:31:15904 /** Zoom the viewport so that the page height consumes the entire viewport. */
dstockwella685a702019-01-29 02:21:54905 fitToHeight() {
Henrique Nakashima50b18e02017-11-21 23:29:57906 this.fitToHeightInternal_(true);
dstockwella685a702019-01-29 02:21:54907 }
Henrique Nakashima50b18e02017-11-21 23:29:57908
909 /**
Henrique Nakashima50b18e02017-11-21 23:29:57910 * Zoom the viewport so that a page consumes as much as possible of the it.
rbpotter84b2076a2019-08-23 01:31:15911 * @param {boolean} scrollToTopOfPage Whether the viewport should be scrolled
912 * to the top of the current page. If false, the viewport will remain at
913 * the current scroll position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42914 * @private
[email protected]3528d6302014-02-19 08:13:07915 */
dstockwella685a702019-01-29 02:21:54916 fitToPageInternal_(scrollToTopOfPage) {
dpapad9afc2802017-08-09 22:01:43917 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57918 this.fittingType_ = FittingType.FIT_TO_PAGE;
Dan Beamd1cca6e2019-01-03 02:46:27919 if (!this.documentDimensions_) {
[email protected]499e9562014-06-26 05:45:27920 return;
Dan Beamd1cca6e2019-01-03 02:46:27921 }
dstockwellafba4e42018-12-02 22:58:06922 const page = this.getMostVisiblePage();
sammc07f53aa2014-11-06 06:57:46923 // Fit to the current page's height and the widest page's width.
dstockwellafba4e42018-12-02 22:58:06924 const dimensions = {
sammc07f53aa2014-11-06 06:57:46925 width: this.documentDimensions_.width,
926 height: this.pageDimensions_[page].height,
927 };
Henrique Nakashima50b18e02017-11-21 23:29:57928 this.setZoomInternal_(this.computeFittingZoom_(dimensions, true, true));
raymesbd82a942015-08-05 07:05:18929 if (scrollToTopOfPage) {
rbpotter84b2076a2019-08-23 01:31:15930 this.position = {
931 x: 0,
932 y: this.pageDimensions_[page].y * this.getZoom()
933 };
raymesbd82a942015-08-05 07:05:18934 }
[email protected]499e9562014-06-26 05:45:27935 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43936 });
dstockwella685a702019-01-29 02:21:54937 }
[email protected]3528d6302014-02-19 08:13:07938
939 /**
raymes161514f2015-02-17 05:09:51940 * Zoom the viewport so that a page consumes the entire viewport. Also scrolls
941 * the viewport to the top of the current page.
942 */
dstockwella685a702019-01-29 02:21:54943 fitToPage() {
raymes161514f2015-02-17 05:09:51944 this.fitToPageInternal_(true);
dstockwella685a702019-01-29 02:21:54945 }
raymes161514f2015-02-17 05:09:51946
rbpotter84b2076a2019-08-23 01:31:15947 /** Zoom the viewport to the default zoom. */
dstockwella685a702019-01-29 02:21:54948 fitToNone() {
Henrique Nakashima4cf9ed42018-09-07 21:02:03949 this.mightZoom_(() => {
950 this.fittingType_ = FittingType.NONE;
Dan Beamd1cca6e2019-01-03 02:46:27951 if (!this.documentDimensions_) {
Henrique Nakashima4cf9ed42018-09-07 21:02:03952 return;
Dan Beamd1cca6e2019-01-03 02:46:27953 }
Henrique Nakashima4cf9ed42018-09-07 21:02:03954 this.setZoomInternal_(Math.min(
955 this.defaultZoom_,
956 this.computeFittingZoom_(this.documentDimensions_, true, false)));
957 this.updateViewport_();
958 });
dstockwella685a702019-01-29 02:21:54959 }
Henrique Nakashima4cf9ed42018-09-07 21:02:03960
rbpotter84b2076a2019-08-23 01:31:15961 /** Zoom out to the next predefined zoom level. */
dstockwella685a702019-01-29 02:21:54962 zoomOut() {
dpapad9afc2802017-08-09 22:01:43963 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57964 this.fittingType_ = FittingType.NONE;
Brian Clifton08b57c02019-12-18 01:29:36965 let nextZoom = this.presetZoomFactors_[0];
966 for (let i = 0; i < this.presetZoomFactors_.length; i++) {
967 if (this.presetZoomFactors_[i] < this.internalZoom_) {
968 nextZoom = this.presetZoomFactors_[i];
Dan Beamd1cca6e2019-01-03 02:46:27969 }
[email protected]499e9562014-06-26 05:45:27970 }
[email protected]fbad5bb2014-07-18 07:20:36971 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27972 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43973 });
dstockwella685a702019-01-29 02:21:54974 }
[email protected]3528d6302014-02-19 08:13:07975
rbpotter84b2076a2019-08-23 01:31:15976 /** Zoom in to the next predefined zoom level. */
dstockwella685a702019-01-29 02:21:54977 zoomIn() {
dpapad9afc2802017-08-09 22:01:43978 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57979 this.fittingType_ = FittingType.NONE;
Brian Clifton08b57c02019-12-18 01:29:36980 const maxZoomIndex = this.presetZoomFactors_.length - 1;
981 let nextZoom = this.presetZoomFactors_[maxZoomIndex];
982 for (let i = maxZoomIndex; i >= 0; i--) {
983 if (this.presetZoomFactors_[i] > this.internalZoom_) {
984 nextZoom = this.presetZoomFactors_[i];
Dan Beamd1cca6e2019-01-03 02:46:27985 }
[email protected]499e9562014-06-26 05:45:27986 }
[email protected]fbad5bb2014-07-18 07:20:36987 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27988 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43989 });
dstockwella685a702019-01-29 02:21:54990 }
[email protected]3528d6302014-02-19 08:13:07991
992 /**
mcnee6e1abbf2016-11-09 18:05:31993 * Pinch zoom event handler.
994 * @param {!Object} e The pinch event.
995 */
dstockwella685a702019-01-29 02:21:54996 pinchZoom(e) {
dpapad9afc2802017-08-09 22:01:43997 this.mightZoom_(() => {
rbpotterb43063eb2020-02-20 01:23:10998 this.pinchPhase_ = e.direction === 'out' ?
dbeam70db0fb2017-06-19 17:09:27999 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_OUT :
1000 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_IN;
mcnee6e1abbf2016-11-09 18:05:311001
dstockwellafba4e42018-12-02 22:58:061002 const scaleDelta = e.startScaleRatio / this.prevScale_;
dstockwella685a702019-01-29 02:21:541003 if (this.firstPinchCenterInFrame_ != null) {
1004 this.pinchPanVector_ =
1005 vectorDelta(e.center, this.firstPinchCenterInFrame_);
1006 }
mcnee6e1abbf2016-11-09 18:05:311007
dstockwellafba4e42018-12-02 22:58:061008 const needsScrollbars =
dbeam70db0fb2017-06-19 17:09:271009 this.documentNeedsScrollbars_(this.zoomManager_.applyBrowserZoom(
Brian Clifton08b57c02019-12-18 01:29:361010 this.clampZoom_(this.internalZoom_ * scaleDelta)));
mcnee6e1abbf2016-11-09 18:05:311011
1012 this.pinchCenter_ = e.center;
1013
1014 // If there's no horizontal scrolling, keep the content centered so the
1015 // user can't zoom in on the non-content area.
1016 // TODO(mcnee) Investigate other ways of scaling when we don't have
1017 // horizontal scrolling. We want to keep the document centered,
1018 // but this causes a potentially awkward transition when we start
1019 // using the gesture center.
1020 if (!needsScrollbars.horizontal) {
1021 this.pinchCenter_ = {
1022 x: this.window_.innerWidth / 2,
1023 y: this.window_.innerHeight / 2
1024 };
1025 } else if (this.keepContentCentered_) {
rbpotter84b2076a2019-08-23 01:31:151026 this.oldCenterInContent_ =
1027 this.frameToContent_(frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:311028 this.keepContentCentered_ = false;
1029 }
1030
dbeam70db0fb2017-06-19 17:09:271031 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:311032 this.updateViewport_();
1033 this.prevScale_ = e.startScaleRatio;
dpapad9afc2802017-08-09 22:01:431034 });
dstockwella685a702019-01-29 02:21:541035 }
mcnee6e1abbf2016-11-09 18:05:311036
dstockwella685a702019-01-29 02:21:541037 /** @param {!Object} e The pinch event. */
1038 pinchZoomStart(e) {
mcnee6e1abbf2016-11-09 18:05:311039 this.pinchPhase_ = Viewport.PinchPhase.PINCH_START;
1040 this.prevScale_ = 1;
rbpotter84b2076a2019-08-23 01:31:151041 this.oldCenterInContent_ =
1042 this.frameToContent_(frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:311043
rbpotter84b2076a2019-08-23 01:31:151044 const needsScrollbars = this.documentNeedsScrollbars_(this.getZoom());
mcnee6e1abbf2016-11-09 18:05:311045 this.keepContentCentered_ = !needsScrollbars.horizontal;
1046 // We keep track of begining of the pinch.
1047 // By doing so we will be able to compute the pan distance.
1048 this.firstPinchCenterInFrame_ = e.center;
dstockwella685a702019-01-29 02:21:541049 }
mcnee6e1abbf2016-11-09 18:05:311050
dstockwella685a702019-01-29 02:21:541051 /** @param {!Object} e The pinch event. */
1052 pinchZoomEnd(e) {
dpapad9afc2802017-08-09 22:01:431053 this.mightZoom_(() => {
mcnee6e1abbf2016-11-09 18:05:311054 this.pinchPhase_ = Viewport.PinchPhase.PINCH_END;
dstockwellafba4e42018-12-02 22:58:061055 const scaleDelta = e.startScaleRatio / this.prevScale_;
rbpotter84b2076a2019-08-23 01:31:151056 this.pinchCenter_ = /** @type {!Point} */ (e.center);
mcnee6e1abbf2016-11-09 18:05:311057
dbeam70db0fb2017-06-19 17:09:271058 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:311059 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:431060 });
mcnee6e1abbf2016-11-09 18:05:311061
1062 this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
1063 this.pinchPanVector_ = null;
1064 this.pinchCenter_ = null;
1065 this.firstPinchCenterInFrame_ = null;
dstockwella685a702019-01-29 02:21:541066 }
mcnee6e1abbf2016-11-09 18:05:311067
1068 /**
Jeremy Chinsenfb6768e2019-08-17 01:09:071069 * Go to the next page. If the document is in two-up view, go to the left page
1070 * of the next row.
Jeremy Chinsen168926a92019-08-16 16:22:151071 */
1072 goToNextPage() {
Jeremy Chinsenfb6768e2019-08-17 01:09:071073 const currentPage = this.getMostVisiblePage();
Hui Yingst73ba19a2020-03-10 19:45:211074 const nextPageOffset =
1075 (this.twoUpViewEnabled() && currentPage % 2 === 0) ? 2 : 1;
Jeremy Chinsenfb6768e2019-08-17 01:09:071076 this.goToPage(currentPage + nextPageOffset);
Jeremy Chinsen168926a92019-08-16 16:22:151077 }
1078
1079 /**
Jeremy Chinsenfb6768e2019-08-17 01:09:071080 * Go to the previous page. If the document is in two-up view, go to the left
1081 * page of the previous row.
Jeremy Chinsen168926a92019-08-16 16:22:151082 */
1083 goToPreviousPage() {
Jeremy Chinsenfb6768e2019-08-17 01:09:071084 const currentPage = this.getMostVisiblePage();
1085 let previousPageOffset = -1;
1086
Hui Yingst73ba19a2020-03-10 19:45:211087 if (this.twoUpViewEnabled()) {
rbpotterb43063eb2020-02-20 01:23:101088 previousPageOffset = (currentPage % 2 === 0) ? -2 : -3;
Jeremy Chinsenfb6768e2019-08-17 01:09:071089 }
1090
1091 this.goToPage(currentPage + previousPageOffset);
Jeremy Chinsen168926a92019-08-16 16:22:151092 }
1093
1094 /**
[email protected]3528d6302014-02-19 08:13:071095 * Go to the given page index.
[email protected]f90b9a42014-08-20 05:37:341096 * @param {number} page the index of the page to go to. zero-based.
[email protected]3528d6302014-02-19 08:13:071097 */
dstockwella685a702019-01-29 02:21:541098 goToPage(page) {
Henrique Nakashima85fc58b32017-12-11 21:53:421099 this.goToPageAndXY(page, 0, 0);
dstockwella685a702019-01-29 02:21:541100 }
Henrique Nakashima9d9e0632017-10-06 21:38:181101
1102 /**
1103 * Go to the given y position in the given page index.
1104 * @param {number} page the index of the page to go to. zero-based.
Henrique Nakashima85fc58b32017-12-11 21:53:421105 * @param {number} x the x position in the page to go to.
Henrique Nakashima9d9e0632017-10-06 21:38:181106 * @param {number} y the y position in the page to go to.
1107 */
dstockwella685a702019-01-29 02:21:541108 goToPageAndXY(page, x, y) {
dpapad9afc2802017-08-09 22:01:431109 this.mightZoom_(() => {
Dan Beamd1cca6e2019-01-03 02:46:271110 if (this.pageDimensions_.length === 0) {
[email protected]499e9562014-06-26 05:45:271111 return;
Dan Beamd1cca6e2019-01-03 02:46:271112 }
1113 if (page < 0) {
[email protected]499e9562014-06-26 05:45:271114 page = 0;
Dan Beamd1cca6e2019-01-03 02:46:271115 }
1116 if (page >= this.pageDimensions_.length) {
[email protected]499e9562014-06-26 05:45:271117 page = this.pageDimensions_.length - 1;
Dan Beamd1cca6e2019-01-03 02:46:271118 }
dstockwellafba4e42018-12-02 22:58:061119 const dimensions = this.pageDimensions_[page];
1120 let toolbarOffset = 0;
Henrique Nakashima50b18e02017-11-21 23:29:571121 // Unless we're in fit to page or fit to height mode, scroll above the
1122 // page by |this.topToolbarHeight_| so that the toolbar isn't covering it
raymesbd82a942015-08-05 07:05:181123 // initially.
Dan Beamd1cca6e2019-01-03 02:46:271124 if (!this.isPagedMode()) {
raymesbd82a942015-08-05 07:05:181125 toolbarOffset = this.topToolbarHeight_;
Dan Beamd1cca6e2019-01-03 02:46:271126 }
raymesbd82a942015-08-05 07:05:181127 this.position = {
rbpotter84b2076a2019-08-23 01:31:151128 x: (dimensions.x + x) * this.getZoom(),
1129 y: (dimensions.y + y) * this.getZoom() - toolbarOffset
raymesbd82a942015-08-05 07:05:181130 };
[email protected]499e9562014-06-26 05:45:271131 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:431132 });
dstockwella685a702019-01-29 02:21:541133 }
[email protected]3528d6302014-02-19 08:13:071134
1135 /**
rbpotter84b2076a2019-08-23 01:31:151136 * @param {DocumentDimensions} documentDimensions The dimensions of the
dstockwella685a702019-01-29 02:21:541137 * document
[email protected]3528d6302014-02-19 08:13:071138 */
dstockwella685a702019-01-29 02:21:541139 setDocumentDimensions(documentDimensions) {
dpapad9afc2802017-08-09 22:01:431140 this.mightZoom_(() => {
dstockwellafba4e42018-12-02 22:58:061141 const initialDimensions = !this.documentDimensions_;
[email protected]499e9562014-06-26 05:45:271142 this.documentDimensions_ = documentDimensions;
1143 this.pageDimensions_ = this.documentDimensions_.pageDimensions;
1144 if (initialDimensions) {
dbeam70db0fb2017-06-19 17:09:271145 this.setZoomInternal_(Math.min(
1146 this.defaultZoom_,
Henrique Nakashima50b18e02017-11-21 23:29:571147 this.computeFittingZoom_(this.documentDimensions_, true, false)));
dbeam70db0fb2017-06-19 17:09:271148 this.position = {x: 0, y: -this.topToolbarHeight_};
[email protected]499e9562014-06-26 05:45:271149 }
1150 this.contentSizeChanged_();
1151 this.resize_();
dpapad9afc2802017-08-09 22:01:431152 });
dstockwella685a702019-01-29 02:21:541153 }
[email protected]312112c72014-04-14 01:45:431154
1155 /**
Douglas Stockwell1752f7762018-11-28 23:41:361156 * @param {number} page
dstockwella685a702019-01-29 02:21:541157 * @return {ViewportRect} The bounds for page `page` minus the shadows.
Douglas Stockwell1752f7762018-11-28 23:41:361158 */
dstockwella685a702019-01-29 02:21:541159 getPageInsetDimensions(page) {
Douglas Stockwell1752f7762018-11-28 23:41:361160 const pageDimensions = this.pageDimensions_[page];
1161 const shadow = Viewport.PAGE_SHADOW;
1162 return {
1163 x: pageDimensions.x + shadow.left,
1164 y: pageDimensions.y + shadow.top,
1165 width: pageDimensions.width - shadow.left - shadow.right,
1166 height: pageDimensions.height - shadow.top - shadow.bottom,
1167 };
dstockwella685a702019-01-29 02:21:541168 }
Douglas Stockwell1752f7762018-11-28 23:41:361169
1170 /**
[email protected]312112c72014-04-14 01:45:431171 * Get the coordinates of the page contents (excluding the page shadow)
1172 * relative to the screen.
rbpotter84b2076a2019-08-23 01:31:151173 * @param {number} page The index of the page to get the rect for.
1174 * @return {!ViewportRect} A rect representing the page in screen coordinates.
[email protected]312112c72014-04-14 01:45:431175 */
dstockwella685a702019-01-29 02:21:541176 getPageScreenRect(page) {
[email protected]499e9562014-06-26 05:45:271177 if (!this.documentDimensions_) {
dbeam70db0fb2017-06-19 17:09:271178 return {x: 0, y: 0, width: 0, height: 0};
[email protected]499e9562014-06-26 05:45:271179 }
Dan Beamd1cca6e2019-01-03 02:46:271180 if (page >= this.pageDimensions_.length) {
[email protected]312112c72014-04-14 01:45:431181 page = this.pageDimensions_.length - 1;
Dan Beamd1cca6e2019-01-03 02:46:271182 }
[email protected]312112c72014-04-14 01:45:431183
dstockwellafba4e42018-12-02 22:58:061184 const pageDimensions = this.pageDimensions_[page];
[email protected]312112c72014-04-14 01:45:431185
1186 // Compute the page dimensions minus the shadows.
dstockwellafba4e42018-12-02 22:58:061187 const insetDimensions = this.getPageInsetDimensions(page);
[email protected]312112c72014-04-14 01:45:431188
[email protected]345e7c62014-05-02 09:52:581189 // Compute the x-coordinate of the page within the document.
1190 // TODO(raymes): This should really be set when the PDF plugin passes the
1191 // page coordinates, but it isn't yet.
dstockwellafba4e42018-12-02 22:58:061192 const x = (this.documentDimensions_.width - pageDimensions.width) / 2 +
[email protected]345e7c62014-05-02 09:52:581193 Viewport.PAGE_SHADOW.left;
1194 // Compute the space on the left of the document if the document fits
1195 // completely in the screen.
rbpotter84b2076a2019-08-23 01:31:151196 const zoom = this.getZoom();
dstockwellafba4e42018-12-02 22:58:061197 let spaceOnLeft =
rbpotter84b2076a2019-08-23 01:31:151198 (this.size.width - this.documentDimensions_.width * zoom) / 2;
[email protected]345e7c62014-05-02 09:52:581199 spaceOnLeft = Math.max(spaceOnLeft, 0);
[email protected]312112c72014-04-14 01:45:431200
1201 return {
rbpotter84b2076a2019-08-23 01:31:151202 x: x * zoom + spaceOnLeft - this.window_.pageXOffset,
1203 y: insetDimensions.y * zoom - this.window_.pageYOffset,
1204 width: insetDimensions.width * zoom,
1205 height: insetDimensions.height * zoom
[email protected]312112c72014-04-14 01:45:431206 };
dstockwella685a702019-01-29 02:21:541207 }
Henrique Nakashima50b18e02017-11-21 23:29:571208
1209 /**
1210 * Check if the current fitting type is a paged mode.
Henrique Nakashima50b18e02017-11-21 23:29:571211 * In a paged mode, page up and page down scroll to the top of the
1212 * previous/next page and part of the page is under the toolbar.
Henrique Nakashima50b18e02017-11-21 23:29:571213 * @return {boolean} Whether the current fitting type is a paged mode.
1214 */
dstockwella685a702019-01-29 02:21:541215 isPagedMode() {
Henrique Nakashima50b18e02017-11-21 23:29:571216 return (
rbpotterb43063eb2020-02-20 01:23:101217 this.fittingType_ === FittingType.FIT_TO_PAGE ||
1218 this.fittingType_ === FittingType.FIT_TO_HEIGHT);
dstockwella685a702019-01-29 02:21:541219 }
Henrique Nakashima585c7af02018-03-27 04:55:211220
rbpotterde9429d2019-09-12 00:40:081221 /**
1222 * @param {!PartialPoint} point The position to which to scroll the viewport.
1223 */
dstockwella685a702019-01-29 02:21:541224 scrollTo(point) {
Henrique Nakashima585c7af02018-03-27 04:55:211225 let changed = false;
1226 const newPosition = this.position;
rbpotterb43063eb2020-02-20 01:23:101227 if (point.x !== undefined && point.x !== newPosition.x) {
Henrique Nakashima585c7af02018-03-27 04:55:211228 newPosition.x = point.x;
1229 changed = true;
1230 }
rbpotterb43063eb2020-02-20 01:23:101231 if (point.y !== undefined && point.y !== newPosition.y) {
Henrique Nakashima585c7af02018-03-27 04:55:211232 newPosition.y = point.y;
1233 changed = true;
1234 }
1235
Dan Beamd1cca6e2019-01-03 02:46:271236 if (changed) {
Henrique Nakashima585c7af02018-03-27 04:55:211237 this.position = newPosition;
Dan Beamd1cca6e2019-01-03 02:46:271238 }
dstockwella685a702019-01-29 02:21:541239 }
Henrique Nakashima585c7af02018-03-27 04:55:211240
rbpotter84b2076a2019-08-23 01:31:151241 /** @param {!Point} delta The delta by which to scroll the viewport. */
dstockwella685a702019-01-29 02:21:541242 scrollBy(delta) {
Henrique Nakashima585c7af02018-03-27 04:55:211243 const newPosition = this.position;
1244 newPosition.x += delta.x;
1245 newPosition.y += delta.y;
1246 this.scrollTo(newPosition);
[email protected]3528d6302014-02-19 08:13:071247 }
rbpotter84b2076a2019-08-23 01:31:151248
1249 /** Removes all events being tracked from the tracker. */
1250 resetTracker() {
1251 if (this.tracker_) {
1252 this.tracker_.removeAll();
1253 }
1254 }
dstockwella685a702019-01-29 02:21:541255}
rbpotter84b2076a2019-08-23 01:31:151256
1257/**
1258 * Enumeration of pinch states.
1259 * This should match PinchPhase enum in pdf/out_of_process_instance.h
1260 * @enum {number}
1261 */
1262Viewport.PinchPhase = {
1263 PINCH_NONE: 0,
1264 PINCH_START: 1,
1265 PINCH_UPDATE_ZOOM_OUT: 2,
1266 PINCH_UPDATE_ZOOM_IN: 3,
1267 PINCH_END: 4
1268};
1269
rbpotter4b8484f2020-05-22 04:52:261270// The increment to scroll a page by in pixels when up/down/left/right arrow
1271// keys are pressed. Usually we just let the browser handle scrolling on the
1272// window when these keys are pressed but in certain cases we need to simulate
1273// these events.
rbpotter84b2076a2019-08-23 01:31:151274Viewport.SCROLL_INCREMENT = 40;
1275
rbpotter4b8484f2020-05-22 04:52:261276// The width of the page shadow around pages in pixels.
rbpotter84b2076a2019-08-23 01:31:151277Viewport.PAGE_SHADOW = {
1278 top: 3,
1279 bottom: 7,
1280 left: 5,
1281 right: 5
1282};