Commit 3528d630 authored by raymes@chromium.org's avatar raymes@chromium.org

Implement viewporting for the out of process PDF plugin.

BUG=303491

Review URL: https://codereview.chromium.org/137663008

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@251993 0039d316-1c4b-4281-b951-d872f2087c98
parent 8370a622
......@@ -214,7 +214,8 @@
<include name="IDR_PDF_INDEX_HTML" file="pdf/index.html" allowexternalscript="true" type="BINDATA" />
<include name="IDR_PDF_INDEX_JS" file="pdf/index.js" type="BINDATA" />
<include name="IDR_PDF_BACKGROUND_JS" file="pdf/background.js" type="BINDATA" />
<include name="IDR_PDF_JS" file="pdf/pdf.js" type="BINDATA" />
<include name="IDR_PDF_JS" file="pdf/pdf.js" type="BINDATA" flattenhtml="true" />
<include name="IDR_PDF_VIEWPORT_JS" file="pdf/viewport.js" type="BINDATA" />
<include name="IDR_PDF_POLYMER_JS" file="pdf/polymer_loader.js" type="BINDATA" flattenhtml="true" />
<include name="IDR_PDF_BUTTON_LOW_1" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_page.png" type="BINDATA" />
<include name="IDR_PDF_BUTTON_LOW_2" file="pdf/html_office/elements/viewer-button/img/lowDPI/button_fit_width.png" type="BINDATA" />
......
......@@ -172,20 +172,20 @@ license that can be found in the LICENSE file.
</polymer-element>
<polymer-element name="viewer-toolbar" attributes="fadingIn" on-mouseover="{{fadeIn}}" on-mousemove="{{fadeIn}}" on-mouseout="{{fadeOut}}" assetpath="html_office/elements/viewer-toolbar/">
<template>
<style>/* Copyright 2013 The Chromium Authors. All rights reserved.
<template>
<style>/* Copyright 2013 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
:host {
-webkit-transition: opacity 0.4s ease-in-out;
-webkit-transition: opacity 400ms ease-in-out;
bottom: 0;
display: block;
font-size: 0;
opacity: 1;
padding: 30px 30px 15px 30vw;
position: fixed;
right: 0;
padding: 30px 30px 15px 30vw;
}
#toolbar {
......@@ -194,16 +194,16 @@ license that can be found in the LICENSE file.
overflow: hidden;
}
</style>
<div id="toolbar">
<content></content>
</div>
</template>
<div id="toolbar">
<content></content>
</div>
</template>
</polymer-element>
<polymer-element name="viewer-button" attributes="src latchable" assetpath="html_office/elements/viewer-button/">
<template>
<style>/* Copyright 2013 The Chromium Authors. All rights reserved.
<template>
<style>/* Copyright 2013 The Chromium Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
......@@ -240,9 +240,9 @@ license that can be found in the LICENSE file.
background-image: none;
}
</style>
<div id="icon"></div>
</template>
<div id="icon"></div>
</template>
</polymer-element>
<style>
body {
......@@ -252,22 +252,35 @@ license that can be found in the LICENSE file.
viewer-toolbar {
z-index: 2;
}
object {
#plugin {
height: 100%;
position: fixed;
width: 100%;
z-index: 1;
}
#sizer {
position: absolute;
z-index: 0;
}
</style>
</head>
<body>
<viewer-toolbar>
<div id="sizer"></div>
<viewer-toolbar id="toolbar">
<polymer-selector>
<viewer-button src="button_fit_page.png" latchable="true"></viewer-button>
<viewer-button src="button_fit_width.png" latchable="true"></viewer-button>
<viewer-button src="button_zoom_in.png"></viewer-button>
<viewer-button src="button_zoom_out.png"></viewer-button>
<viewer-button id="fit-to-page-button" src="button_fit_page.png" latchable="">
</viewer-button>
<viewer-button id="fit-to-width-button" src="button_fit_width.png" latchable="">
</viewer-button>
<viewer-button id="zoom-in-button" src="button_zoom_in.png">
</viewer-button>
<viewer-button id="zoom-out-button" src="button_zoom_out.png">
</viewer-button>
</polymer-selector>
<viewer-button src="button_save.png"></viewer-button>
<viewer-button src="button_print.png"></viewer-button>
<viewer-button id="save-button" src="button_save.png"></viewer-button>
<viewer-button id="print-button" src="button_print.png"></viewer-button>
</viewer-toolbar>
......
......@@ -19,22 +19,36 @@
viewer-toolbar {
z-index: 2;
}
object {
#plugin {
height: 100%;
position: fixed;
width: 100%;
z-index: 1;
}
#sizer {
position: absolute;
z-index: 0;
}
</style>
</head>
<body>
<viewer-toolbar>
<div id="sizer"></div>
<viewer-toolbar id="toolbar">
<polymer-selector>
<viewer-button src="button_fit_page.png" latchable=true></viewer-button>
<viewer-button src="button_fit_width.png" latchable=true></viewer-button>
<viewer-button src="button_zoom_in.png"></viewer-button>
<viewer-button src="button_zoom_out.png"></viewer-button>
<viewer-button id="fit-to-page-button" src="button_fit_page.png" latchable>
</viewer-button>
<viewer-button id="fit-to-width-button" src="button_fit_width.png"
latchable>
</viewer-button>
<viewer-button id="zoom-in-button" src="button_zoom_in.png">
</viewer-button>
<viewer-button id="zoom-out-button" src="button_zoom_out.png">
</viewer-button>
</polymer-selector>
<viewer-button src="button_save.png"></viewer-button>
<viewer-button src="button_print.png"></viewer-button>
<viewer-button id="save-button" src="button_save.png"></viewer-button>
<viewer-button id="print-button" src="button_print.png"></viewer-button>
</viewer-toolbar>
</body>
......
......@@ -387,39 +387,39 @@ Polymer={},"function"==typeof window.Polymer&&(Polymer={}),function(a){function
});
;
Polymer('viewer-toolbar', {
fadingIn: false,
timerId: undefined,
ready: function() {
this.fadingInChanged();
},
fadeIn: function() {
this.fadingIn = true;
},
fadeOut: function() {
this.fadingIn = false;
},
fadingInChanged: function() {
if (this.fadingIn) {
this.style.opacity = 1;
if (this.timerId !== undefined) {
clearTimeout(this.timerId);
this.timerId = undefined;
}
} else {
if (this.timerId === undefined) {
this.timerId = setTimeout(
function() {
this.style.opacity = 0;
this.timerId = undefined;
}.bind(this), 3000);
}
Polymer('viewer-toolbar', {
fadingIn: false,
timerId: undefined,
ready: function() {
this.fadingInChanged();
},
fadeIn: function() {
this.fadingIn = true;
},
fadeOut: function() {
this.fadingIn = false;
},
fadingInChanged: function() {
if (this.fadingIn) {
this.style.opacity = 1;
if (this.timerId !== undefined) {
clearTimeout(this.timerId);
this.timerId = undefined;
}
} else {
if (this.timerId === undefined) {
this.timerId = setTimeout(
function() {
this.style.opacity = 0;
this.timerId = undefined;
}.bind(this), 3000);
}
}
});
;
}
});
;
(function() {
(function() {
var dpi = '';
Polymer('viewer-button', {
......@@ -435,7 +435,7 @@ Polymer={},"function"==typeof window.Polymer&&(Polymer={}),function(a){function
if (this.src) {
this.$.icon.style.backgroundImage =
'url(' + this.getAttribute('assetpath') + 'img/' + dpi +
'DPI/' + this.src + ')';
'DPI/' + this.src + ')';
} else {
this.$.icon.style.backgroundImage = '';
}
......@@ -447,5 +447,4 @@ Polymer={},"function"==typeof window.Polymer&&(Polymer={}),function(a){function
this.classList.remove('latchable');
},
});
})();
\ No newline at end of file
})();
......@@ -3,51 +3,121 @@
// found in the LICENSE file.
(function() {
'use strict';
<include src="../../../../ui/webui/resources/js/util.js"></include>
<include src="viewport.js"></include>
// The plugin element is sized to fill the entire window and is set to be fixed
// positioning, acting as a viewport. The plugin renders into this viewport
// according to the scroll position of the window.
var plugin;
// This element is placed behind the plugin element to cause scrollbars to be
// displayed in the window. It is sized according to the document size of the
// pdf and zoom level.
var sizer;
function onScroll() {
var scrollMessage = {
type: 'scroll',
xOffset: window.pageXOffset,
yOffset: window.pageYOffset
};
plugin.postMessage(scrollMessage);
// The toolbar element.
var viewerToolbar;
// The viewport object.
var viewport;
// Returns true if the fit-to-page button is enabled.
function isFitToPageEnabled() {
return $('fit-to-page-button').classList.contains('polymer-selected');
}
// Called when a message is received from the plugin.
function handleMessage(message) {
if (message.data['type'] == 'document_dimensions') {
if (sizer.style.height != message.data['document_height'] + 'px') {
sizer.style.height = message.data['document_height'] + 'px';
sizer.style.width = message.data['document_width'] + 'px';
}
if (message.data.type == 'documentDimensions') {
viewport.setDocumentDimensions(message.data);
}
}
// Callback that's called when the viewport changes.
function viewportChangedCallback(zoom, x, y, scrollbarWidth, hasScrollbars) {
// Offset the toolbar position so that it doesn't move if scrollbars appear.
var toolbarRight = hasScrollbars.y ? 0 : scrollbarWidth;
var toolbarBottom = hasScrollbars.x ? 0 : scrollbarWidth;
viewerToolbar.style.right = toolbarRight + 'px';
viewerToolbar.style.bottom = toolbarBottom + 'px';
// Notify the plugin of the viewport change.
plugin.postMessage({
type: 'viewport',
zoom: zoom,
xOffset: x,
yOffset: y
});
}
function load() {
window.addEventListener('scroll',
function() { webkitRequestAnimationFrame(onScroll); });
sizer = $('sizer');
viewerToolbar = $('toolbar');
// Create the viewport.
viewport = new Viewport(window,
sizer,
isFitToPageEnabled,
viewportChangedCallback);
// Create the plugin object dynamically so we can set its src.
plugin = document.createElement('object');
plugin.id = 'plugin';
plugin.type = 'application/x-google-chrome-pdf';
plugin.addEventListener('message', handleMessage, false);
// The pdf location is passed in the document url in the format:
// http://<.../pdf.html>?<pdf location>.
var url = window.location.search.substring(1);
plugin = document.createElement('object');
plugin.setAttribute('width', '100%');
plugin.setAttribute('height', '100%');
plugin.setAttribute('type', 'application/x-google-chrome-pdf');
plugin.setAttribute('src', url);
plugin.style.zIndex = '1';
plugin.style.position = 'fixed';
plugin.addEventListener('message', handleMessage, false);
document.body.appendChild(plugin);
sizer = document.createElement('div');
sizer.style.zIndex = '0';
sizer.style.position = 'absolute';
sizer.style.width = '100%';
sizer.style.height = '100%';
document.body.appendChild(sizer);
// Setup the button event listeners.
$('fit-to-width-button').addEventListener('click',
viewport.fitToWidth.bind(viewport));
$('fit-to-page-button').addEventListener('click',
viewport.fitToPage.bind(viewport));
$('zoom-in-button').addEventListener('click',
viewport.zoomIn.bind(viewport));
$('zoom-out-button').addEventListener('click',
viewport.zoomOut.bind(viewport));
// Setup keyboard event listeners.
document.onkeydown = function(e) {
switch (e.keyCode) {
case 37: // Left arrow key.
// Go to the previous page if there are no horizontal scrollbars.
if (!viewport.documentHasScrollbars().x) {
viewport.goToPage(viewport.getMostVisiblePage() - 1);
// Since we do the movement of the page
e.preventDefault();
}
return;
case 33: // Page up key.
// Go to the previous page if we are fit-to-page.
if (isFitToPageEnabled()) {
viewport.goToPage(viewport.getMostVisiblePage() - 1);
e.preventDefault();
}
return;
case 39: // Right arrow key.
// Go to the next page if there are no horizontal scrollbars.
if (!viewport.documentHasScrollbars().x) {
viewport.goToPage(viewport.getMostVisiblePage() + 1);
e.preventDefault();
}
return;
case 34: // Page down key.
// Go to the next page if we are fit-to-page.
if (isFitToPageEnabled()) {
viewport.goToPage(viewport.getMostVisiblePage() + 1);
e.preventDefault();
}
return;
}
};
}
load();
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Predefined zoom factors to be used when zooming in/out. These are in
* ascending order.
*/
var ZOOM_FACTORS = [0.25, 0.333, 0.5, 0.666, 0.75, 0.9, 1,
1.1, 1.25, 1.5, 1.75, 2, 2.5, 3, 4, 5];
/**
* Returns the area of the intersection of two rectangles.
* @param {Object} rect1 the first rect
* @param {Object} rect2 the second rect
* @return {number} the area of the intersection of the rects
*/
function getIntersectionArea(rect1, rect2) {
var xOverlap = Math.max(0,
Math.min(rect1.x + rect1.width, rect2.x + rect2.width) -
Math.max(rect1.x, rect2.x));
var yOverlap = Math.max(0,
Math.min(rect1.y + rect1.height, rect2.y + rect2.height) -
Math.max(rect1.y, rect2.y));
return xOverlap * yOverlap;
}
/**
* @return {number} width of a scrollbar in pixels
*/
function getScrollbarWidth() {
var parentDiv = document.createElement('div');
parentDiv.style.visibility = 'hidden';
var parentDivWidth = 500;
parentDiv.style.width = parentDivWidth + 'px';
document.body.appendChild(parentDiv);
parentDiv.style.overflow = 'scroll';
var childDiv = document.createElement('div');
childDiv.style.width = '100%';
parentDiv.appendChild(childDiv);
var childDivWidth = childDiv.offsetWidth;
parentDiv.parentNode.removeChild(parentDiv);
return parentDivWidth - childDivWidth;
}
/**
* Create a new viewport.
* @param {Window} window the window
* @param {Object} sizer is the element which represents the size of the
* document in the viewport
* @param {Function} fitToPageEnabledFunction returns true if fit-to-page is
* enabled
* @param {Function} viewportChangedCallback is run when the viewport changes
*/
function Viewport(window,
sizer,
fitToPageEnabledFunction,
viewportChangedCallback) {
this.window_ = window;
this.sizer_ = sizer;
this.fitToPageEnabledFunction_ = fitToPageEnabledFunction;
this.viewportChangedCallback_ = viewportChangedCallback;
this.zoom_ = 1;
this.documentDimensions_ = {};
this.pageDimensions_ = [];
this.scrollbarWidth_ = getScrollbarWidth();
window.addEventListener('scroll', this.updateViewport_.bind(this));
}
Viewport.prototype = {
/**
* @private
* Returns true if the document needs scrollbars at the given zoom level.
* @param {number} zoom compute whether scrollbars are needed at this zoom
* @return {Object} with 'x' and 'y' keys which map to bool values
* indicating if the horizontal and vertical scrollbars are needed
* respectively.
*/
documentNeedsScrollbars_: function(zoom) {
return {
x: this.documentDimensions_.width * zoom > this.window_.innerWidth,
y: this.documentDimensions_.height * zoom > this.window_.innerHeight
};
},
/**
* Returns true if the document needs scrollbars at the current zoom level.
* @return {Object} with 'x' and 'y' keys which map to bool values
* indicating if the horizontal and vertical scrollbars are needed
* respectively.
*/
documentHasScrollbars: function() {
return this.documentNeedsScrollbars_(this.zoom_);
},
/**
* @private
* Helper function called when the zoomed document size changes.
*/
contentSizeChanged_: function() {
this.sizer_.style.width =
this.documentDimensions_.width * this.zoom_ + 'px';
this.sizer_.style.height =
this.documentDimensions_.height * this.zoom_ + 'px';
},
/**
* Sets the zoom of the viewport.
* @param {number} newZoom the zoom level to zoom to
*/
setZoom: function(newZoom) {
var oldZoom = this.zoom_;
this.zoom_ = newZoom;
// Record the scroll position (relative to the middle of the window).
var currentScrollPos = [
(this.window_.scrollX + this.window_.innerWidth / 2) / oldZoom,
(this.window_.scrollY + this.window_.innerHeight / 2) / oldZoom
];
this.contentSizeChanged_();
// Scroll to the scaled scroll position.
this.window_.scrollTo(
currentScrollPos[0] * newZoom - this.window_.innerWidth / 2,
currentScrollPos[1] * newZoom - this.window_.innerHeight / 2);
},
/**
* @private
* Called when the viewport should be updated.
*/
updateViewport_: function() {
// Shift the toolbar so that it doesn't move when the scrollbars display
var needsScrollbars = this.documentHasScrollbars();
this.viewportChangedCallback_(this.zoom_,
this.window_.pageXOffset,
this.window_.pageYOffset,
this.scrollbarWidth_,
needsScrollbars);
},
/**
* @private
* Returns a rect representing the current viewport.
* @return {Object} a rect representing the current viewport.
*/
getCurrentViewportRect_: function() {
return {
x: this.window_.pageXOffset / this.zoom_,
y: this.window_.pageYOffset / this.zoom_,
width: this.window_.innerWidth / this.zoom_,
height: this.window_.innerHeight / this.zoom_,
};
},
/**
* Returns the page with the most pixels in the current viewport.
* @return {int} the index of the most visible page.
*/
getMostVisiblePage: function() {
// TODO(raymes): Do a binary search here.
var mostVisiblePage = {'number': 0, 'area': 0};
for (var i = 0; i < this.pageDimensions_.length; i++) {
var area = getIntersectionArea(this.pageDimensions_[i],
this.getCurrentViewportRect_());
if (area > mostVisiblePage.area) {
mostVisiblePage.area = area;
mostVisiblePage.number = i;
}
}
return mostVisiblePage.number;
},
/**
* @private
* Compute the zoom level for fit-to-page or fit-to-width. |pageDimensions| is
* the dimensions for a given page and if |widthOnly| is true, it indicates
* that fit-to-page zoom should be computed rather than fit-to-page.
* @param {Object} pageDimensions the dimensions of a given page
* @param {boolean} widthOnly a bool indicating whether fit-to-page or
* fit-to-width should be computed.
* @return {number} the zoom to use
*/
computeFittingZoom_: function(pageDimensions, widthOnly) {
// First compute the zoom without scrollbars.
var zoomWidth = this.window_.innerWidth / pageDimensions.width;
var zoom;
if (widthOnly) {
zoom = zoomWidth;
} else {
var zoomHeight = this.window_.innerHeight / pageDimensions.height;
zoom = Math.min(zoomWidth, zoomHeight);
}
// Check if there needs to be any scrollbars.
var needsScrollbars = this.documentNeedsScrollbars_(zoom);
// If the document fits, just return the zoom.
if (!needsScrollbars.x && !needsScrollbars.y)
return zoom;
var zoomedDimensions = {
width: this.documentDimensions_.width * zoom,
height: this.documentDimensions_.height * zoom
};
// Check if adding a scrollbar will result in needing the other scrollbar.
var scrollbarWidth = this.scrollbarWidth_;
if (needsScrollbars.x &&
zoomedDimensions.height > this.window_.innerHeight - scrollbarWidth) {
needsScrollbars.y = true;
}
if (needsScrollbars.y &&
zoomedDimensions.width > this.window_.innerWidth - scrollbarWidth) {
needsScrollbars.x = true;
}
// Compute available window space.
var windowWithScrollbars = {
width: this.window_.innerWidth,
height: this.window_.innerHeight
};
if (needsScrollbars.x)
windowWithScrollbars.height -= scrollbarWidth;
if (needsScrollbars.y)
windowWithScrollbars.width -= scrollbarWidth;
// Recompute the zoom.
zoomWidth = windowWithScrollbars.width / pageDimensions.width;
if (widthOnly) {
zoom = zoomWidth;
} else {
var zoomHeight = windowWithScrollbars.height / pageDimensions.height;
zoom = Math.min(zoomWidth, zoomHeight);
}
return zoom;
},
/**
* Zoom the viewport so that the page-width consumes the entire viewport.
*/
fitToWidth: function() {
var page = this.getMostVisiblePage();
this.setZoom(this.computeFittingZoom_(this.pageDimensions_[page], true));
this.window_.scrollTo(this.pageDimensions_[page].x * this.zoom_,
this.window_.scrollY);
this.updateViewport_();
},
/**
* Zoom the viewport so that a page consumes the entire viewport. Also scrolls
* to the top of the most visible page.
*/
fitToPage: function() {
var page = this.getMostVisiblePage();
this.setZoom(this.computeFittingZoom_(this.pageDimensions_[page], false));
this.window_.scrollTo(this.pageDimensions_[page].x * this.zoom_,
this.pageDimensions_[page].y * this.zoom_);
this.updateViewport_();
},
/**
* Zoom out to the next predefined zoom level.
*/
zoomOut: function() {
var nextZoom = ZOOM_FACTORS[0];
for (var i = 0; i < ZOOM_FACTORS.length; i++) {
if (ZOOM_FACTORS[i] < this.zoom_)
nextZoom = ZOOM_FACTORS[i];
}
this.setZoom(nextZoom);
this.updateViewport_();
},
/**
* Zoom in to the next predefined zoom level.
*/
zoomIn: function() {
var nextZoom = ZOOM_FACTORS[ZOOM_FACTORS.length - 1];
for (var i = ZOOM_FACTORS.length - 1; i >= 0; i--) {
if (ZOOM_FACTORS[i] > this.zoom_)
nextZoom = ZOOM_FACTORS[i];
}
this.setZoom(nextZoom);
this.updateViewport_();
},
/**
* Go to the given page index.
* @param {number} page the index of the page to go to
*/
goToPage: function(page) {
if (page < 0)
page = 0;
var dimensions = this.pageDimensions_[page];
this.window_.scrollTo(dimensions.x * this.zoom_, dimensions.y * this.zoom_);
},
/**
* Set the dimensions of the document.
* @param {Object} documentDimensions the dimensions of the document
*/
setDocumentDimensions: function(documentDimensions) {
this.documentDimensions_ = documentDimensions;
this.pageDimensions_ = this.documentDimensions_.pageDimensions;
this.contentSizeChanged_();
this.updateViewport_();
}
};
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment