blob: 71f0933113cbb501cdf95ac63fb901e8192d2059 [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) {
248 if (!this.documentDimensions_)
249 return null;
250 return {
251 width: Math.round(this.documentDimensions_.width * zoom),
252 height: Math.round(this.documentDimensions_.height * zoom)
253 };
254 },
255
256 /**
[email protected]3528d6302014-02-19 08:13:07257 * Returns true if the document needs scrollbars at the given zoom level.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42258 *
[email protected]3528d6302014-02-19 08:13:07259 * @param {number} zoom compute whether scrollbars are needed at this zoom
[email protected]312112c72014-04-14 01:45:43260 * @return {Object} with 'horizontal' and 'vertical' keys which map to bool
261 * values indicating if the horizontal and vertical scrollbars are needed
[email protected]3528d6302014-02-19 08:13:07262 * respectively.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42263 * @private
[email protected]3528d6302014-02-19 08:13:07264 */
265 documentNeedsScrollbars_: function(zoom) {
dstockwellafba4e42018-12-02 22:58:06266 const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
raymese6e90c62015-08-10 06:21:40267 if (!zoomedDimensions) {
dbeam70db0fb2017-06-19 17:09:27268 return {horizontal: false, vertical: false};
[email protected]499e9562014-06-26 05:45:27269 }
sammc63334fed2014-11-10 03:07:35270
271 // If scrollbars are required for one direction, expand the document in the
272 // other direction to take the width of the scrollbars into account when
273 // deciding whether the other direction needs scrollbars.
raymese6e90c62015-08-10 06:21:40274 if (zoomedDimensions.width > this.window_.innerWidth)
275 zoomedDimensions.height += this.scrollbarWidth_;
276 else if (zoomedDimensions.height > this.window_.innerHeight)
277 zoomedDimensions.width += this.scrollbarWidth_;
[email protected]3528d6302014-02-19 08:13:07278 return {
raymese6e90c62015-08-10 06:21:40279 horizontal: zoomedDimensions.width > this.window_.innerWidth,
raymes051fb2c2015-09-21 04:56:41280 vertical: zoomedDimensions.height + this.topToolbarHeight_ >
281 this.window_.innerHeight
[email protected]3528d6302014-02-19 08:13:07282 };
283 },
284
285 /**
286 * Returns true if the document needs scrollbars at the current zoom level.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42287 *
[email protected]3528d6302014-02-19 08:13:07288 * @return {Object} with 'x' and 'y' keys which map to bool values
289 * indicating if the horizontal and vertical scrollbars are needed
290 * respectively.
291 */
292 documentHasScrollbars: function() {
mcnee2999413a2016-12-06 20:29:25293 return this.documentNeedsScrollbars_(this.zoom);
[email protected]3528d6302014-02-19 08:13:07294 },
295
296 /**
[email protected]3528d6302014-02-19 08:13:07297 * Helper function called when the zoomed document size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42298 *
299 * @private
[email protected]3528d6302014-02-19 08:13:07300 */
301 contentSizeChanged_: function() {
dstockwellafba4e42018-12-02 22:58:06302 const zoomedDimensions = this.getZoomedDocumentDimensions_(this.zoom);
raymese6e90c62015-08-10 06:21:40303 if (zoomedDimensions) {
304 this.sizer_.style.width = zoomedDimensions.width + 'px';
dbeam70db0fb2017-06-19 17:09:27305 this.sizer_.style.height =
306 zoomedDimensions.height + this.topToolbarHeight_ + 'px';
[email protected]345e7c62014-05-02 09:52:58307 }
[email protected]3528d6302014-02-19 08:13:07308 },
309
310 /**
[email protected]312112c72014-04-14 01:45:43311 * Called when the viewport should be updated.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42312 *
313 * @private
[email protected]3528d6302014-02-19 08:13:07314 */
[email protected]312112c72014-04-14 01:45:43315 updateViewport_: function() {
316 this.viewportChangedCallback_();
317 },
318
319 /**
rbpottera82dacc2017-09-08 19:39:39320 * Called when the browser window size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42321 *
322 * @private
rbpottera82dacc2017-09-08 19:39:39323 */
324 resizeWrapper_: function() {
325 this.setUserInitiatedCallback_(false);
326 this.resize_();
327 this.setUserInitiatedCallback_(true);
328 },
329
330 /**
[email protected]312112c72014-04-14 01:45:43331 * Called when the viewport size changes.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42332 *
333 * @private
[email protected]312112c72014-04-14 01:45:43334 */
335 resize_: function() {
Henrique Nakashima50b18e02017-11-21 23:29:57336 if (this.fittingType_ == FittingType.FIT_TO_PAGE)
raymes161514f2015-02-17 05:09:51337 this.fitToPageInternal_(false);
Henrique Nakashima50b18e02017-11-21 23:29:57338 else if (this.fittingType_ == FittingType.FIT_TO_WIDTH)
[email protected]312112c72014-04-14 01:45:43339 this.fitToWidth();
Henrique Nakashima50b18e02017-11-21 23:29:57340 else if (this.fittingType_ == FittingType.FIT_TO_HEIGHT)
341 this.fitToHeightInternal_(false);
Henrique Nakashima4cf9ed42018-09-07 21:02:03342 else if (this.internalZoom_ == 0)
343 this.fitToNone();
[email protected]312112c72014-04-14 01:45:43344 else
345 this.updateViewport_();
346 },
347
348 /**
Douglas Stockwell1752f7762018-11-28 23:41:36349 * @type {Point} the scroll position of the viewport.
[email protected]312112c72014-04-14 01:45:43350 */
351 get position() {
352 return {
353 x: this.window_.pageXOffset,
raymesbd82a942015-08-05 07:05:18354 y: this.window_.pageYOffset - this.topToolbarHeight_
[email protected]312112c72014-04-14 01:45:43355 };
356 },
357
358 /**
359 * Scroll the viewport to the specified position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42360 *
Douglas Stockwell1752f7762018-11-28 23:41:36361 * @type {Point} position The position to scroll to.
[email protected]312112c72014-04-14 01:45:43362 */
363 set position(position) {
raymesbd82a942015-08-05 07:05:18364 this.window_.scrollTo(position.x, position.y + this.topToolbarHeight_);
[email protected]312112c72014-04-14 01:45:43365 },
366
367 /**
368 * @type {Object} the size of the viewport excluding scrollbars.
369 */
370 get size() {
dstockwellafba4e42018-12-02 22:58:06371 const needsScrollbars = this.documentNeedsScrollbars_(this.zoom);
372 const scrollbarWidth = needsScrollbars.vertical ? this.scrollbarWidth_ : 0;
373 const scrollbarHeight =
374 needsScrollbars.horizontal ? this.scrollbarWidth_ : 0;
[email protected]312112c72014-04-14 01:45:43375 return {
376 width: this.window_.innerWidth - scrollbarWidth,
377 height: this.window_.innerHeight - scrollbarHeight
378 };
379 },
380
381 /**
382 * @type {number} the zoom level of the viewport.
383 */
384 get zoom() {
mcnee2999413a2016-12-06 20:29:25385 return this.zoomManager_.applyBrowserZoom(this.internalZoom_);
386 },
387
388 /**
389 * Set the zoom manager.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42390 *
mcnee2999413a2016-12-06 20:29:25391 * @type {ZoomManager} manager the zoom manager to set.
392 */
393 set zoomManager(manager) {
394 this.zoomManager_ = manager;
[email protected]312112c72014-04-14 01:45:43395 },
396
397 /**
mcnee6e1abbf2016-11-09 18:05:31398 * @type {Viewport.PinchPhase} The phase of the current pinch gesture for
399 * the viewport.
400 */
401 get pinchPhase() {
402 return this.pinchPhase_;
403 },
404
405 /**
406 * @type {Object} The panning caused by the current pinch gesture (as
407 * the deltas of the x and y coordinates).
408 */
409 get pinchPanVector() {
410 return this.pinchPanVector_;
411 },
412
413 /**
414 * @type {Object} The coordinates of the center of the current pinch gesture.
415 */
416 get pinchCenter() {
417 return this.pinchCenter_;
418 },
419
420 /**
[email protected]499e9562014-06-26 05:45:27421 * Used to wrap a function that might perform zooming on the viewport. This is
422 * required so that we can notify the plugin that zooming is in progress
423 * so that while zooming is taking place it can stop reacting to scroll events
424 * from the viewport. This is to avoid flickering.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42425 *
Douglas Stockwell1752f7762018-11-28 23:41:36426 * @param {Function} f Function to wrap
Henrique Nakashimad5de6d0d2018-04-13 18:02:42427 * @private
[email protected]499e9562014-06-26 05:45:27428 */
429 mightZoom_: function(f) {
430 this.beforeZoomCallback_();
431 this.allowedToChangeZoom_ = true;
432 f();
433 this.allowedToChangeZoom_ = false;
434 this.afterZoomCallback_();
435 },
436
437 /**
[email protected]312112c72014-04-14 01:45:43438 * Sets the zoom of the viewport.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42439 *
[email protected]312112c72014-04-14 01:45:43440 * @param {number} newZoom the zoom level to zoom to.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42441 * @private
[email protected]312112c72014-04-14 01:45:43442 */
[email protected]fbad5bb2014-07-18 07:20:36443 setZoomInternal_: function(newZoom) {
dstockwell154f9c42019-01-01 23:36:34444 assert(
445 this.allowedToChangeZoom_,
446 'Called Viewport.setZoomInternal_ without calling ' +
447 'Viewport.mightZoom_.');
sammc5c33b7e2014-11-07 04:03:09448 // Record the scroll position (relative to the top-left of the window).
dstockwellafba4e42018-12-02 22:58:06449 const currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25450 x: this.position.x / this.zoom,
451 y: this.position.y / this.zoom
raymesbd82a942015-08-05 07:05:18452 };
Henrique Nakashima4cf9ed42018-09-07 21:02:03453
mcnee2999413a2016-12-06 20:29:25454 this.internalZoom_ = newZoom;
[email protected]3528d6302014-02-19 08:13:07455 this.contentSizeChanged_();
456 // Scroll to the scaled scroll position.
raymesbd82a942015-08-05 07:05:18457 this.position = {
mcnee2999413a2016-12-06 20:29:25458 x: currentScrollPos.x * this.zoom,
459 y: currentScrollPos.y * this.zoom
raymesbd82a942015-08-05 07:05:18460 };
[email protected]3528d6302014-02-19 08:13:07461 },
462
463 /**
mcnee6e1abbf2016-11-09 18:05:31464 * Sets the zoom of the viewport.
465 * Same as setZoomInternal_ but for pinch zoom we have some more operations.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42466 *
mcnee6e1abbf2016-11-09 18:05:31467 * @param {number} scaleDelta The zoom delta.
468 * @param {!Object} center The pinch center in content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42469 * @private
mcnee6e1abbf2016-11-09 18:05:31470 */
471 setPinchZoomInternal_: function(scaleDelta, center) {
dbeam70db0fb2017-06-19 17:09:27472 assert(
473 this.allowedToChangeZoom_,
mcnee6e1abbf2016-11-09 18:05:31474 'Called Viewport.setPinchZoomInternal_ without calling ' +
dbeam70db0fb2017-06-19 17:09:27475 'Viewport.mightZoom_.');
Kevin McNee4e78edc2017-10-26 19:55:54476 this.internalZoom_ = Viewport.clampZoom(this.internalZoom_ * scaleDelta);
mcnee6e1abbf2016-11-09 18:05:31477
dstockwellafba4e42018-12-02 22:58:06478 const newCenterInContent = this.frameToContent(center);
479 const delta = {
mcnee6e1abbf2016-11-09 18:05:31480 x: (newCenterInContent.x - this.oldCenterInContent.x),
481 y: (newCenterInContent.y - this.oldCenterInContent.y)
482 };
483
484 // Record the scroll position (relative to the pinch center).
dstockwellafba4e42018-12-02 22:58:06485 const currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25486 x: this.position.x - delta.x * this.zoom,
487 y: this.position.y - delta.y * this.zoom
mcnee6e1abbf2016-11-09 18:05:31488 };
489
490 this.contentSizeChanged_();
491 // Scroll to the scaled scroll position.
dbeam70db0fb2017-06-19 17:09:27492 this.position = {x: currentScrollPos.x, y: currentScrollPos.y};
mcnee6e1abbf2016-11-09 18:05:31493 },
494
495 /**
mcnee6e1abbf2016-11-09 18:05:31496 * Converts a point from frame to content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42497 *
mcnee6e1abbf2016-11-09 18:05:31498 * @param {!Object} framePoint The frame coordinates.
499 * @return {!Object} The content coordinates.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42500 * @private
mcnee6e1abbf2016-11-09 18:05:31501 */
502 frameToContent: function(framePoint) {
503 // TODO(mcnee) Add a helper Point class to avoid duplicating operations
504 // on plain {x,y} objects.
505 return {
mcnee2999413a2016-12-06 20:29:25506 x: (framePoint.x + this.position.x) / this.zoom,
507 y: (framePoint.y + this.position.y) / this.zoom
mcnee6e1abbf2016-11-09 18:05:31508 };
509 },
510
511 /**
[email protected]fbad5bb2014-07-18 07:20:36512 * Sets the zoom to the given zoom level.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42513 *
[email protected]fbad5bb2014-07-18 07:20:36514 * @param {number} newZoom the zoom level to zoom to.
[email protected]499e9562014-06-26 05:45:27515 */
[email protected]fbad5bb2014-07-18 07:20:36516 setZoom: function(newZoom) {
Henrique Nakashima50b18e02017-11-21 23:29:57517 this.fittingType_ = FittingType.NONE;
dpapad9afc2802017-08-09 22:01:43518 this.mightZoom_(() => {
Kevin McNee4e78edc2017-10-26 19:55:54519 this.setZoomInternal_(Viewport.clampZoom(newZoom));
[email protected]fbad5bb2014-07-18 07:20:36520 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43521 });
[email protected]499e9562014-06-26 05:45:27522 },
523
524 /**
mcnee2999413a2016-12-06 20:29:25525 * Gets notified of the browser zoom changing seperately from the
526 * internal zoom.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42527 *
mcnee2999413a2016-12-06 20:29:25528 * @param {number} oldBrowserZoom the previous value of the browser zoom.
529 */
530 updateZoomFromBrowserChange: function(oldBrowserZoom) {
dpapad9afc2802017-08-09 22:01:43531 this.mightZoom_(() => {
mcnee2999413a2016-12-06 20:29:25532 // Record the scroll position (relative to the top-left of the window).
dstockwellafba4e42018-12-02 22:58:06533 const oldZoom = oldBrowserZoom * this.internalZoom_;
534 const currentScrollPos = {
mcnee2999413a2016-12-06 20:29:25535 x: this.position.x / oldZoom,
536 y: this.position.y / oldZoom
537 };
538 this.contentSizeChanged_();
539 // Scroll to the scaled scroll position.
540 this.position = {
541 x: currentScrollPos.x * this.zoom,
542 y: currentScrollPos.y * this.zoom
543 };
544 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43545 });
mcnee2999413a2016-12-06 20:29:25546 },
547
548 /**
[email protected]312112c72014-04-14 01:45:43549 * @type {number} the width of scrollbars in the viewport in pixels.
[email protected]3528d6302014-02-19 08:13:07550 */
[email protected]312112c72014-04-14 01:45:43551 get scrollbarWidth() {
552 return this.scrollbarWidth_;
[email protected]3528d6302014-02-19 08:13:07553 },
554
555 /**
Henrique Nakashima50b18e02017-11-21 23:29:57556 * @type {FittingType} the fitting type the viewport is currently in.
[email protected]3528d6302014-02-19 08:13:07557 */
[email protected]312112c72014-04-14 01:45:43558 get fittingType() {
559 return this.fittingType_;
[email protected]3528d6302014-02-19 08:13:07560 },
561
562 /**
Henrique Nakashimad5de6d0d2018-04-13 18:02:42563 * Get the which page is at a given y position.
564 *
dpapadd2d64772017-05-19 02:30:38565 * @param {number} y the y-coordinate to get the page at.
566 * @return {number} the index of a page overlapping the given y-coordinate.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42567 * @private
[email protected]56b7e3c2014-02-20 04:31:24568 */
569 getPageAtY_: function(y) {
dstockwellafba4e42018-12-02 22:58:06570 let min = 0;
571 let max = this.pageDimensions_.length - 1;
[email protected]56b7e3c2014-02-20 04:31:24572 while (max >= min) {
dstockwellafba4e42018-12-02 22:58:06573 const page = Math.floor(min + ((max - min) / 2));
[email protected]13df2a42014-02-27 03:50:41574 // There might be a gap between the pages, in which case use the bottom
575 // of the previous page as the top for finding the page.
dstockwellafba4e42018-12-02 22:58:06576 let top = 0;
[email protected]13df2a42014-02-27 03:50:41577 if (page > 0) {
578 top = this.pageDimensions_[page - 1].y +
579 this.pageDimensions_[page - 1].height;
580 }
dstockwellafba4e42018-12-02 22:58:06581 const bottom =
dbeam70db0fb2017-06-19 17:09:27582 this.pageDimensions_[page].y + this.pageDimensions_[page].height;
[email protected]13df2a42014-02-27 03:50:41583
[email protected]56b7e3c2014-02-20 04:31:24584 if (top <= y && bottom > y)
585 return page;
Lei Zhangddc0ef032017-11-22 02:14:24586
587 if (top > y)
[email protected]56b7e3c2014-02-20 04:31:24588 max = page - 1;
589 else
590 min = page + 1;
591 }
592 return 0;
593 },
594
595 /**
sammcd06823a962014-11-10 04:51:15596 * Returns the page with the greatest proportion of its height in the current
597 * viewport.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42598 *
dpapadd2d64772017-05-19 02:30:38599 * @return {number} the index of the most visible page.
[email protected]3528d6302014-02-19 08:13:07600 */
601 getMostVisiblePage: function() {
dstockwellafba4e42018-12-02 22:58:06602 const firstVisiblePage = this.getPageAtY_(this.position.y / this.zoom);
sammcd06823a962014-11-10 04:51:15603 if (firstVisiblePage == this.pageDimensions_.length - 1)
604 return firstVisiblePage;
605
dstockwellafba4e42018-12-02 22:58:06606 const viewportRect = {
mcnee2999413a2016-12-06 20:29:25607 x: this.position.x / this.zoom,
608 y: this.position.y / this.zoom,
609 width: this.size.width / this.zoom,
610 height: this.size.height / this.zoom
[email protected]345e7c62014-05-02 09:52:58611 };
dstockwellafba4e42018-12-02 22:58:06612 const firstVisiblePageVisibility =
dbeam70db0fb2017-06-19 17:09:27613 getIntersectionHeight(
614 this.pageDimensions_[firstVisiblePage], viewportRect) /
sammcd06823a962014-11-10 04:51:15615 this.pageDimensions_[firstVisiblePage].height;
dstockwellafba4e42018-12-02 22:58:06616 const nextPageVisibility =
dbeam70db0fb2017-06-19 17:09:27617 getIntersectionHeight(
618 this.pageDimensions_[firstVisiblePage + 1], viewportRect) /
sammcd06823a962014-11-10 04:51:15619 this.pageDimensions_[firstVisiblePage + 1].height;
620 if (nextPageVisibility > firstVisiblePageVisibility)
621 return firstVisiblePage + 1;
622 return firstVisiblePage;
[email protected]3528d6302014-02-19 08:13:07623 },
624
625 /**
Henrique Nakashima50b18e02017-11-21 23:29:57626 * Compute the zoom level for fit-to-page, fit-to-width or fit-to-height.
627 *
628 * At least one of {fitWidth, fitHeight} must be true.
629 *
630 * @param {Object} pageDimensions the dimensions of a given page in px.
631 * @param {boolean} fitWidth a bool indicating whether the whole width of the
632 * page needs to be in the viewport.
633 * @param {boolean} fitHeight a bool indicating whether the whole height of
634 * the page needs to be in the viewport.
mcnee2999413a2016-12-06 20:29:25635 * @return {number} the internal zoom to set
Henrique Nakashimad5de6d0d2018-04-13 18:02:42636 * @private
[email protected]3528d6302014-02-19 08:13:07637 */
Henrique Nakashima50b18e02017-11-21 23:29:57638 computeFittingZoom_: function(pageDimensions, fitWidth, fitHeight) {
639 assert(
640 fitWidth || fitHeight,
641 'Invalid parameters. At least one of fitWidth and fitHeight must be ' +
642 'true.');
643
[email protected]3528d6302014-02-19 08:13:07644 // First compute the zoom without scrollbars.
dstockwellafba4e42018-12-02 22:58:06645 let zoom = this.computeFittingZoomGivenDimensions_(
Henrique Nakashima50b18e02017-11-21 23:29:57646 fitWidth, fitHeight, this.window_.innerWidth, this.window_.innerHeight,
647 pageDimensions.width, pageDimensions.height);
648
[email protected]3528d6302014-02-19 08:13:07649 // Check if there needs to be any scrollbars.
dstockwellafba4e42018-12-02 22:58:06650 const needsScrollbars = this.documentNeedsScrollbars_(zoom);
[email protected]3528d6302014-02-19 08:13:07651
652 // If the document fits, just return the zoom.
[email protected]312112c72014-04-14 01:45:43653 if (!needsScrollbars.horizontal && !needsScrollbars.vertical)
[email protected]3528d6302014-02-19 08:13:07654 return zoom;
655
dstockwellafba4e42018-12-02 22:58:06656 const zoomedDimensions = this.getZoomedDocumentDimensions_(zoom);
[email protected]3528d6302014-02-19 08:13:07657
658 // Check if adding a scrollbar will result in needing the other scrollbar.
dstockwellafba4e42018-12-02 22:58:06659 const scrollbarWidth = this.scrollbarWidth_;
[email protected]312112c72014-04-14 01:45:43660 if (needsScrollbars.horizontal &&
[email protected]3528d6302014-02-19 08:13:07661 zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43662 needsScrollbars.vertical = true;
[email protected]3528d6302014-02-19 08:13:07663 }
[email protected]312112c72014-04-14 01:45:43664 if (needsScrollbars.vertical &&
[email protected]3528d6302014-02-19 08:13:07665 zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) {
[email protected]312112c72014-04-14 01:45:43666 needsScrollbars.horizontal = true;
[email protected]3528d6302014-02-19 08:13:07667 }
668
669 // Compute available window space.
dstockwellafba4e42018-12-02 22:58:06670 const windowWithScrollbars = {
[email protected]3528d6302014-02-19 08:13:07671 width: this.window_.innerWidth,
672 height: this.window_.innerHeight
673 };
[email protected]312112c72014-04-14 01:45:43674 if (needsScrollbars.horizontal)
[email protected]3528d6302014-02-19 08:13:07675 windowWithScrollbars.height -= scrollbarWidth;
[email protected]312112c72014-04-14 01:45:43676 if (needsScrollbars.vertical)
[email protected]3528d6302014-02-19 08:13:07677 windowWithScrollbars.width -= scrollbarWidth;
678
679 // Recompute the zoom.
Henrique Nakashima50b18e02017-11-21 23:29:57680 zoom = this.computeFittingZoomGivenDimensions_(
681 fitWidth, fitHeight, windowWithScrollbars.width,
682 windowWithScrollbars.height, pageDimensions.width,
683 pageDimensions.height);
684
mcnee2999413a2016-12-06 20:29:25685 return this.zoomManager_.internalZoomComponent(zoom);
[email protected]3528d6302014-02-19 08:13:07686 },
687
688 /**
Henrique Nakashima50b18e02017-11-21 23:29:57689 * Compute a zoom level given the dimensions to fit and the actual numbers
690 * in those dimensions.
691 *
692 * @param {boolean} fitWidth make sure the page width is totally contained in
693 * the window.
694 * @param {boolean} fitHeight make sure the page height is totally contained
695 * in the window.
696 * @param {number} windowWidth the width of the window in px.
697 * @param {number} windowHeight the height of the window in px.
698 * @param {number} pageWidth the width of the page in px.
699 * @param {number} pageHeight the height of the page in px.
700 * @return {number} the internal zoom to set
Henrique Nakashimad5de6d0d2018-04-13 18:02:42701 * @private
Henrique Nakashima50b18e02017-11-21 23:29:57702 */
703 computeFittingZoomGivenDimensions_: function(
704 fitWidth, fitHeight, windowWidth, windowHeight, pageWidth, pageHeight) {
705 // Assumes at least one of {fitWidth, fitHeight} is set.
dstockwellafba4e42018-12-02 22:58:06706 let zoomWidth;
707 let zoomHeight;
Henrique Nakashima50b18e02017-11-21 23:29:57708
709 if (fitWidth)
710 zoomWidth = windowWidth / pageWidth;
711
712 if (fitHeight)
713 zoomHeight = windowHeight / pageHeight;
714
dstockwellafba4e42018-12-02 22:58:06715 let zoom;
Henrique Nakashima4cf9ed42018-09-07 21:02:03716 if (!fitWidth && fitHeight) {
717 zoom = zoomHeight;
718 } else if (fitWidth && !fitHeight) {
719 zoom = zoomWidth;
720 } else {
721 // Assume fitWidth && fitHeight
722 zoom = Math.min(zoomWidth, zoomHeight);
723 }
Henrique Nakashima50b18e02017-11-21 23:29:57724
Henrique Nakashima4cf9ed42018-09-07 21:02:03725 return Math.max(zoom, 0);
Henrique Nakashima50b18e02017-11-21 23:29:57726 },
727
728 /**
729 * Zoom the viewport so that the page width consumes the entire viewport.
[email protected]3528d6302014-02-19 08:13:07730 */
731 fitToWidth: function() {
dpapad9afc2802017-08-09 22:01:43732 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57733 this.fittingType_ = FittingType.FIT_TO_WIDTH;
[email protected]499e9562014-06-26 05:45:27734 if (!this.documentDimensions_)
735 return;
[email protected]499e9562014-06-26 05:45:27736 // When computing fit-to-width, the maximum width of a page in the
737 // document is used, which is equal to the size of the document width.
dbeam70db0fb2017-06-19 17:09:27738 this.setZoomInternal_(
Henrique Nakashima50b18e02017-11-21 23:29:57739 this.computeFittingZoom_(this.documentDimensions_, true, false));
[email protected]499e9562014-06-26 05:45:27740 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43741 });
[email protected]3528d6302014-02-19 08:13:07742 },
743
744 /**
Henrique Nakashima50b18e02017-11-21 23:29:57745 * Zoom the viewport so that the page height consumes the entire viewport.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42746 *
Henrique Nakashima50b18e02017-11-21 23:29:57747 * @param {boolean} scrollToTopOfPage Set to true if the viewport should be
748 * scrolled to the top of the current page. Set to false if the viewport
749 * should remain at the current scroll position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42750 * @private
Henrique Nakashima50b18e02017-11-21 23:29:57751 */
752 fitToHeightInternal_: function(scrollToTopOfPage) {
753 this.mightZoom_(() => {
754 this.fittingType_ = FittingType.FIT_TO_HEIGHT;
755 if (!this.documentDimensions_)
756 return;
dstockwellafba4e42018-12-02 22:58:06757 const page = this.getMostVisiblePage();
Henrique Nakashima50b18e02017-11-21 23:29:57758 // When computing fit-to-height, the maximum height of the current page
759 // is used.
dstockwellafba4e42018-12-02 22:58:06760 const dimensions = {
Henrique Nakashima50b18e02017-11-21 23:29:57761 width: 0,
762 height: this.pageDimensions_[page].height,
763 };
764 this.setZoomInternal_(this.computeFittingZoom_(dimensions, false, true));
765 if (scrollToTopOfPage) {
766 this.position = {x: 0, y: this.pageDimensions_[page].y * this.zoom};
767 }
768 this.updateViewport_();
769 });
770 },
771
772 /**
773 * Zoom the viewport so that the page height consumes the entire viewport.
774 */
775 fitToHeight: function() {
776 this.fitToHeightInternal_(true);
777 },
778
779 /**
Henrique Nakashima50b18e02017-11-21 23:29:57780 * Zoom the viewport so that a page consumes as much as possible of the it.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42781 *
raymes161514f2015-02-17 05:09:51782 * @param {boolean} scrollToTopOfPage Set to true if the viewport should be
783 * scrolled to the top of the current page. Set to false if the viewport
784 * should remain at the current scroll position.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42785 * @private
[email protected]3528d6302014-02-19 08:13:07786 */
raymes161514f2015-02-17 05:09:51787 fitToPageInternal_: function(scrollToTopOfPage) {
dpapad9afc2802017-08-09 22:01:43788 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57789 this.fittingType_ = FittingType.FIT_TO_PAGE;
[email protected]499e9562014-06-26 05:45:27790 if (!this.documentDimensions_)
791 return;
dstockwellafba4e42018-12-02 22:58:06792 const page = this.getMostVisiblePage();
sammc07f53aa2014-11-06 06:57:46793 // Fit to the current page's height and the widest page's width.
dstockwellafba4e42018-12-02 22:58:06794 const dimensions = {
sammc07f53aa2014-11-06 06:57:46795 width: this.documentDimensions_.width,
796 height: this.pageDimensions_[page].height,
797 };
Henrique Nakashima50b18e02017-11-21 23:29:57798 this.setZoomInternal_(this.computeFittingZoom_(dimensions, true, true));
raymesbd82a942015-08-05 07:05:18799 if (scrollToTopOfPage) {
dbeam70db0fb2017-06-19 17:09:27800 this.position = {x: 0, y: this.pageDimensions_[page].y * this.zoom};
raymesbd82a942015-08-05 07:05:18801 }
[email protected]499e9562014-06-26 05:45:27802 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43803 });
[email protected]3528d6302014-02-19 08:13:07804 },
805
806 /**
raymes161514f2015-02-17 05:09:51807 * Zoom the viewport so that a page consumes the entire viewport. Also scrolls
808 * the viewport to the top of the current page.
809 */
810 fitToPage: function() {
811 this.fitToPageInternal_(true);
812 },
813
814 /**
Henrique Nakashima4cf9ed42018-09-07 21:02:03815 * Zoom the viewport to the default zoom policy.
816 */
817 fitToNone: function() {
818 this.mightZoom_(() => {
819 this.fittingType_ = FittingType.NONE;
820 if (!this.documentDimensions_)
821 return;
822 this.setZoomInternal_(Math.min(
823 this.defaultZoom_,
824 this.computeFittingZoom_(this.documentDimensions_, true, false)));
825 this.updateViewport_();
826 });
827 },
828
829 /**
[email protected]3528d6302014-02-19 08:13:07830 * Zoom out to the next predefined zoom level.
831 */
832 zoomOut: function() {
dpapad9afc2802017-08-09 22:01:43833 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57834 this.fittingType_ = FittingType.NONE;
dstockwellafba4e42018-12-02 22:58:06835 let nextZoom = Viewport.ZOOM_FACTORS[0];
836 for (let i = 0; i < Viewport.ZOOM_FACTORS.length; i++) {
mcnee2999413a2016-12-06 20:29:25837 if (Viewport.ZOOM_FACTORS[i] < this.internalZoom_)
[email protected]499e9562014-06-26 05:45:27838 nextZoom = Viewport.ZOOM_FACTORS[i];
839 }
[email protected]fbad5bb2014-07-18 07:20:36840 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27841 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43842 });
[email protected]3528d6302014-02-19 08:13:07843 },
844
845 /**
846 * Zoom in to the next predefined zoom level.
847 */
848 zoomIn: function() {
dpapad9afc2802017-08-09 22:01:43849 this.mightZoom_(() => {
Henrique Nakashima50b18e02017-11-21 23:29:57850 this.fittingType_ = FittingType.NONE;
dstockwellafba4e42018-12-02 22:58:06851 let nextZoom = Viewport.ZOOM_FACTORS[Viewport.ZOOM_FACTORS.length - 1];
852 for (let i = Viewport.ZOOM_FACTORS.length - 1; i >= 0; i--) {
mcnee2999413a2016-12-06 20:29:25853 if (Viewport.ZOOM_FACTORS[i] > this.internalZoom_)
[email protected]499e9562014-06-26 05:45:27854 nextZoom = Viewport.ZOOM_FACTORS[i];
855 }
[email protected]fbad5bb2014-07-18 07:20:36856 this.setZoomInternal_(nextZoom);
[email protected]499e9562014-06-26 05:45:27857 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43858 });
[email protected]3528d6302014-02-19 08:13:07859 },
860
861 /**
mcnee6e1abbf2016-11-09 18:05:31862 * Pinch zoom event handler.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42863 *
mcnee6e1abbf2016-11-09 18:05:31864 * @param {!Object} e The pinch event.
865 */
866 pinchZoom: function(e) {
dpapad9afc2802017-08-09 22:01:43867 this.mightZoom_(() => {
mcnee6e1abbf2016-11-09 18:05:31868 this.pinchPhase_ = e.direction == 'out' ?
dbeam70db0fb2017-06-19 17:09:27869 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_OUT :
870 Viewport.PinchPhase.PINCH_UPDATE_ZOOM_IN;
mcnee6e1abbf2016-11-09 18:05:31871
dstockwellafba4e42018-12-02 22:58:06872 const scaleDelta = e.startScaleRatio / this.prevScale_;
mcnee6e1abbf2016-11-09 18:05:31873 this.pinchPanVector_ =
874 vectorDelta(e.center, this.firstPinchCenterInFrame_);
875
dstockwellafba4e42018-12-02 22:58:06876 const needsScrollbars =
dbeam70db0fb2017-06-19 17:09:27877 this.documentNeedsScrollbars_(this.zoomManager_.applyBrowserZoom(
Kevin McNee4e78edc2017-10-26 19:55:54878 Viewport.clampZoom(this.internalZoom_ * scaleDelta)));
mcnee6e1abbf2016-11-09 18:05:31879
880 this.pinchCenter_ = e.center;
881
882 // If there's no horizontal scrolling, keep the content centered so the
883 // user can't zoom in on the non-content area.
884 // TODO(mcnee) Investigate other ways of scaling when we don't have
885 // horizontal scrolling. We want to keep the document centered,
886 // but this causes a potentially awkward transition when we start
887 // using the gesture center.
888 if (!needsScrollbars.horizontal) {
889 this.pinchCenter_ = {
890 x: this.window_.innerWidth / 2,
891 y: this.window_.innerHeight / 2
892 };
893 } else if (this.keepContentCentered_) {
894 this.oldCenterInContent =
895 this.frameToContent(frameToPluginCoordinate(e.center));
896 this.keepContentCentered_ = false;
897 }
898
dbeam70db0fb2017-06-19 17:09:27899 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:31900 this.updateViewport_();
901 this.prevScale_ = e.startScaleRatio;
dpapad9afc2802017-08-09 22:01:43902 });
mcnee6e1abbf2016-11-09 18:05:31903 },
904
905 pinchZoomStart: function(e) {
906 this.pinchPhase_ = Viewport.PinchPhase.PINCH_START;
907 this.prevScale_ = 1;
908 this.oldCenterInContent =
909 this.frameToContent(frameToPluginCoordinate(e.center));
910
dstockwellafba4e42018-12-02 22:58:06911 const needsScrollbars = this.documentNeedsScrollbars_(this.zoom);
mcnee6e1abbf2016-11-09 18:05:31912 this.keepContentCentered_ = !needsScrollbars.horizontal;
913 // We keep track of begining of the pinch.
914 // By doing so we will be able to compute the pan distance.
915 this.firstPinchCenterInFrame_ = e.center;
916 },
917
918 pinchZoomEnd: function(e) {
dpapad9afc2802017-08-09 22:01:43919 this.mightZoom_(() => {
mcnee6e1abbf2016-11-09 18:05:31920 this.pinchPhase_ = Viewport.PinchPhase.PINCH_END;
dstockwellafba4e42018-12-02 22:58:06921 const scaleDelta = e.startScaleRatio / this.prevScale_;
mcnee6e1abbf2016-11-09 18:05:31922 this.pinchCenter_ = e.center;
923
dbeam70db0fb2017-06-19 17:09:27924 this.setPinchZoomInternal_(scaleDelta, frameToPluginCoordinate(e.center));
mcnee6e1abbf2016-11-09 18:05:31925 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43926 });
mcnee6e1abbf2016-11-09 18:05:31927
928 this.pinchPhase_ = Viewport.PinchPhase.PINCH_NONE;
929 this.pinchPanVector_ = null;
930 this.pinchCenter_ = null;
931 this.firstPinchCenterInFrame_ = null;
932 },
933
934 /**
[email protected]3528d6302014-02-19 08:13:07935 * Go to the given page index.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42936 *
[email protected]f90b9a42014-08-20 05:37:34937 * @param {number} page the index of the page to go to. zero-based.
[email protected]3528d6302014-02-19 08:13:07938 */
939 goToPage: function(page) {
Henrique Nakashima85fc58b32017-12-11 21:53:42940 this.goToPageAndXY(page, 0, 0);
Henrique Nakashima9d9e0632017-10-06 21:38:18941 },
942
943 /**
944 * Go to the given y position in the given page index.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42945 *
Henrique Nakashima9d9e0632017-10-06 21:38:18946 * @param {number} page the index of the page to go to. zero-based.
Henrique Nakashima85fc58b32017-12-11 21:53:42947 * @param {number} x the x position in the page to go to.
Henrique Nakashima9d9e0632017-10-06 21:38:18948 * @param {number} y the y position in the page to go to.
949 */
Henrique Nakashima85fc58b32017-12-11 21:53:42950 goToPageAndXY: function(page, x, y) {
dpapad9afc2802017-08-09 22:01:43951 this.mightZoom_(() => {
alexandrec9754b9e42015-01-19 03:41:19952 if (this.pageDimensions_.length === 0)
[email protected]499e9562014-06-26 05:45:27953 return;
954 if (page < 0)
955 page = 0;
956 if (page >= this.pageDimensions_.length)
957 page = this.pageDimensions_.length - 1;
dstockwellafba4e42018-12-02 22:58:06958 const dimensions = this.pageDimensions_[page];
959 let toolbarOffset = 0;
Henrique Nakashima50b18e02017-11-21 23:29:57960 // Unless we're in fit to page or fit to height mode, scroll above the
961 // page by |this.topToolbarHeight_| so that the toolbar isn't covering it
raymesbd82a942015-08-05 07:05:18962 // initially.
Henrique Nakashima50b18e02017-11-21 23:29:57963 if (!this.isPagedMode())
raymesbd82a942015-08-05 07:05:18964 toolbarOffset = this.topToolbarHeight_;
965 this.position = {
Henrique Nakashima85fc58b32017-12-11 21:53:42966 x: (dimensions.x + x) * this.zoom,
Henrique Nakashima9d9e0632017-10-06 21:38:18967 y: (dimensions.y + y) * this.zoom - toolbarOffset
raymesbd82a942015-08-05 07:05:18968 };
[email protected]499e9562014-06-26 05:45:27969 this.updateViewport_();
dpapad9afc2802017-08-09 22:01:43970 });
[email protected]3528d6302014-02-19 08:13:07971 },
972
973 /**
974 * Set the dimensions of the document.
Henrique Nakashimad5de6d0d2018-04-13 18:02:42975 *
[email protected]3528d6302014-02-19 08:13:07976 * @param {Object} documentDimensions the dimensions of the document
977 */
978 setDocumentDimensions: function(documentDimensions) {
dpapad9afc2802017-08-09 22:01:43979 this.mightZoom_(() => {
dstockwellafba4e42018-12-02 22:58:06980 const initialDimensions = !this.documentDimensions_;
[email protected]499e9562014-06-26 05:45:27981 this.documentDimensions_ = documentDimensions;
982 this.pageDimensions_ = this.documentDimensions_.pageDimensions;
983 if (initialDimensions) {
dbeam70db0fb2017-06-19 17:09:27984 this.setZoomInternal_(Math.min(
985 this.defaultZoom_,
Henrique Nakashima50b18e02017-11-21 23:29:57986 this.computeFittingZoom_(this.documentDimensions_, true, false)));
dbeam70db0fb2017-06-19 17:09:27987 this.position = {x: 0, y: -this.topToolbarHeight_};
[email protected]499e9562014-06-26 05:45:27988 }
989 this.contentSizeChanged_();
990 this.resize_();
dpapad9afc2802017-08-09 22:01:43991 });
[email protected]312112c72014-04-14 01:45:43992 },
993
994 /**
Douglas Stockwell1752f7762018-11-28 23:41:36995 * @param {number} page
996 * @return {Rect} The bounds for page `page` minus the shadows.
997 */
998 getPageInsetDimensions: function(page) {
999 const pageDimensions = this.pageDimensions_[page];
1000 const shadow = Viewport.PAGE_SHADOW;
1001 return {
1002 x: pageDimensions.x + shadow.left,
1003 y: pageDimensions.y + shadow.top,
1004 width: pageDimensions.width - shadow.left - shadow.right,
1005 height: pageDimensions.height - shadow.top - shadow.bottom,
1006 };
1007 },
1008
1009 /**
[email protected]312112c72014-04-14 01:45:431010 * Get the coordinates of the page contents (excluding the page shadow)
1011 * relative to the screen.
Henrique Nakashimad5de6d0d2018-04-13 18:02:421012 *
[email protected]312112c72014-04-14 01:45:431013 * @param {number} page the index of the page to get the rect for.
1014 * @return {Object} a rect representing the page in screen coordinates.
1015 */
1016 getPageScreenRect: function(page) {
[email protected]499e9562014-06-26 05:45:271017 if (!this.documentDimensions_) {
dbeam70db0fb2017-06-19 17:09:271018 return {x: 0, y: 0, width: 0, height: 0};
[email protected]499e9562014-06-26 05:45:271019 }
[email protected]312112c72014-04-14 01:45:431020 if (page >= this.pageDimensions_.length)
1021 page = this.pageDimensions_.length - 1;
1022
dstockwellafba4e42018-12-02 22:58:061023 const pageDimensions = this.pageDimensions_[page];
[email protected]312112c72014-04-14 01:45:431024
1025 // Compute the page dimensions minus the shadows.
dstockwellafba4e42018-12-02 22:58:061026 const insetDimensions = this.getPageInsetDimensions(page);
[email protected]312112c72014-04-14 01:45:431027
[email protected]345e7c62014-05-02 09:52:581028 // Compute the x-coordinate of the page within the document.
1029 // TODO(raymes): This should really be set when the PDF plugin passes the
1030 // page coordinates, but it isn't yet.
dstockwellafba4e42018-12-02 22:58:061031 const x = (this.documentDimensions_.width - pageDimensions.width) / 2 +
[email protected]345e7c62014-05-02 09:52:581032 Viewport.PAGE_SHADOW.left;
1033 // Compute the space on the left of the document if the document fits
1034 // completely in the screen.
dstockwellafba4e42018-12-02 22:58:061035 let spaceOnLeft =
dbeam70db0fb2017-06-19 17:09:271036 (this.size.width - this.documentDimensions_.width * this.zoom) / 2;
[email protected]345e7c62014-05-02 09:52:581037 spaceOnLeft = Math.max(spaceOnLeft, 0);
[email protected]312112c72014-04-14 01:45:431038
1039 return {
mcnee2999413a2016-12-06 20:29:251040 x: x * this.zoom + spaceOnLeft - this.window_.pageXOffset,
1041 y: insetDimensions.y * this.zoom - this.window_.pageYOffset,
1042 width: insetDimensions.width * this.zoom,
1043 height: insetDimensions.height * this.zoom
[email protected]312112c72014-04-14 01:45:431044 };
Henrique Nakashima50b18e02017-11-21 23:29:571045 },
1046
1047 /**
1048 * Check if the current fitting type is a paged mode.
1049 *
1050 * In a paged mode, page up and page down scroll to the top of the
1051 * previous/next page and part of the page is under the toolbar.
1052 *
1053 * @return {boolean} Whether the current fitting type is a paged mode.
1054 */
1055 isPagedMode: function(page) {
1056 return (
1057 this.fittingType_ == FittingType.FIT_TO_PAGE ||
1058 this.fittingType_ == FittingType.FIT_TO_HEIGHT);
Henrique Nakashima585c7af02018-03-27 04:55:211059 },
1060
1061 /**
1062 * Scroll the viewport to the specified position.
1063 *
1064 * @param {!PartialPoint} point The position to which to move the viewport.
1065 */
1066 scrollTo: function(point) {
1067 let changed = false;
1068 const newPosition = this.position;
1069 if (point.x !== undefined && point.x != newPosition.x) {
1070 newPosition.x = point.x;
1071 changed = true;
1072 }
1073 if (point.y !== undefined && point.y != newPosition.y) {
1074 newPosition.y = point.y;
1075 changed = true;
1076 }
1077
1078 if (changed)
1079 this.position = newPosition;
1080 },
1081
1082 /**
1083 * Scroll the viewport by the specified delta.
1084 *
1085 * @param {!Point} delta The delta by which to move the viewport.
1086 */
1087 scrollBy: function(delta) {
1088 const newPosition = this.position;
1089 newPosition.x += delta.x;
1090 newPosition.y += delta.y;
1091 this.scrollTo(newPosition);
[email protected]3528d6302014-02-19 08:13:071092 }
1093};