Merge pull request #5295 from yurydelendik/pdfviewer

Refactoring to move page display code into separate class
This commit is contained in:
Brendan Dahl 2014-09-29 15:23:35 -07:00
commit 1145eb8c09
17 changed files with 1606 additions and 865 deletions

View File

@ -14,7 +14,7 @@
* limitations under the License.
*/
/* globals chrome, PDFJS, PDFView */
/* globals chrome, PDFJS, PDFViewerApplication */
'use strict';
var ChromeCom = (function ChromeComClosure() {
@ -64,10 +64,10 @@ var ChromeCom = (function ChromeComClosure() {
var streamUrl = response.streamUrl;
if (streamUrl) {
console.log('Found data stream for ' + file);
PDFView.open(streamUrl, 0, undefined, undefined, {
PDFViewerApplication.open(streamUrl, 0, undefined, undefined, {
length: response.contentLength
});
PDFView.setTitleUsingUrl(file);
PDFViewerApplication.setTitleUsingUrl(file);
return;
}
if (isFTPFile && !response.extensionSupportsFTP) {
@ -91,7 +91,7 @@ var ChromeCom = (function ChromeComClosure() {
resolveLocalFileSystemURL(file, function onResolvedFSURL(fileEntry) {
fileEntry.file(function(fileObject) {
var blobUrl = URL.createObjectURL(fileObject);
PDFView.open(blobUrl, 0, undefined, undefined, {
PDFViewerApplication.open(blobUrl, 0, undefined, undefined, {
length: fileObject.size
});
});
@ -100,11 +100,11 @@ var ChromeCom = (function ChromeComClosure() {
// usual way of getting the File's data (via the Web worker).
console.warn('Cannot resolve file ' + file + ', ' + error.name + ' ' +
error.message);
PDFView.open(file, 0);
PDFViewerApplication.open(file, 0);
});
return;
}
PDFView.open(file, 0);
PDFViewerApplication.open(file, 0);
});
};
return ChromeCom;

View File

@ -14,20 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFView, DownloadManager, getFileName */
/* globals DownloadManager, getFileName */
'use strict';
var DocumentAttachmentsView = function documentAttachmentsView(attachments) {
var attachmentsView = document.getElementById('attachmentsView');
var DocumentAttachmentsView = function documentAttachmentsView(options) {
var attachments = options.attachments;
var attachmentsView = options.attachmentsView;
while (attachmentsView.firstChild) {
attachmentsView.removeChild(attachmentsView.firstChild);
}
if (!attachments) {
if (!attachmentsView.classList.contains('hidden')) {
PDFView.switchSidebarView('thumbs');
}
return;
}

View File

@ -14,27 +14,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFView */
'use strict';
var DocumentOutlineView = function documentOutlineView(outline) {
var outlineView = document.getElementById('outlineView');
var DocumentOutlineView = function documentOutlineView(options) {
var outline = options.outline;
var outlineView = options.outlineView;
while (outlineView.firstChild) {
outlineView.removeChild(outlineView.firstChild);
}
if (!outline) {
if (!outlineView.classList.contains('hidden')) {
PDFView.switchSidebarView('thumbs');
}
return;
}
var linkService = options.linkService;
function bindItemLink(domObj, item) {
domObj.href = PDFView.getDestinationHash(item.dest);
domObj.href = linkService.getDestinationHash(item.dest);
domObj.onclick = function documentOutlineViewOnclick(e) {
PDFView.navigateTo(item.dest);
linkService.navigateTo(item.dest);
return false;
};
}

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFView, Promise, mozL10n, getPDFFileNameFromURL, OverlayManager */
/* globals Promise, mozL10n, getPDFFileNameFromURL, OverlayManager */
'use strict';
@ -35,6 +35,8 @@ var DocumentProperties = {
producerField: null,
versionField: null,
pageCountField: null,
url: null,
pdfDocument: null,
initialize: function documentPropertiesInitialize(options) {
this.overlayName = options.overlayName;
@ -72,7 +74,7 @@ var DocumentProperties = {
return;
}
// Get the file size (if it hasn't already been set).
PDFView.pdfDocument.getDownloadInfo().then(function(data) {
this.pdfDocument.getDownloadInfo().then(function(data) {
if (data.length === this.rawFileSize) {
return;
}
@ -81,10 +83,10 @@ var DocumentProperties = {
}.bind(this));
// Get the document properties.
PDFView.pdfDocument.getMetadata().then(function(data) {
this.pdfDocument.getMetadata().then(function(data) {
var fields = [
{ field: this.fileNameField,
content: getPDFFileNameFromURL(PDFView.url) },
content: getPDFFileNameFromURL(this.url) },
{ field: this.fileSizeField, content: this.parseFileSize() },
{ field: this.titleField, content: data.info.Title },
{ field: this.authorField, content: data.info.Author },
@ -97,7 +99,7 @@ var DocumentProperties = {
{ field: this.creatorField, content: data.info.Creator },
{ field: this.producerField, content: data.info.Producer },
{ field: this.versionField, content: data.info.PDFFormatVersion },
{ field: this.pageCountField, content: PDFView.pdfDocument.numPages }
{ field: this.pageCountField, content: this.pdfDocument.numPages }
];
// Show the properties in the dialog.

81
web/interfaces.js Normal file
View File

@ -0,0 +1,81 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/**
* @interface
*/
function IPDFLinkService() {}
IPDFLinkService.prototype = {
/**
* @returns {number}
*/
get page() {},
/**
* @param {number} value
*/
set page(value) {},
/**
* @param dest - The PDF destination object.
*/
navigateTo: function (dest) {},
/**
* @param dest - The PDF destination object.
* @returns {string} The hyperlink to the PDF object.
*/
getDestinationHash: function (dest) {},
/**
* @param hash - The PDF parameters/hash.
* @returns {string} The hyperlink to the PDF object.
*/
getAnchorUrl: function (hash) {},
/**
* @param {string} hash
*/
setHash: function (hash) {},
};
/**
* @interface
*/
function IRenderableView() {}
IRenderableView.prototype = {
/**
* @returns {string} - Unique ID for rendering queue.
*/
get renderingId() {},
/**
* @returns {RenderingStates}
*/
get renderingState() {},
/**
* @param {function} callback - The draw completion callback.
*/
draw: function (callback) {},
resume: function () {},
};
/**
* @interface
*/
function ILastScrollSource() {}
ILastScrollSource.prototype = {
/**
* @returns {number}
*/
get lastScroll() {},
};

View File

@ -14,16 +14,31 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals RenderingStates, PDFView, PDFHistory, PDFJS, mozL10n, CustomStyle,
PresentationMode, scrollIntoView, SCROLLBAR_PADDING, CSS_UNITS,
UNKNOWN_SCALE, DEFAULT_SCALE, getOutputScale, TextLayerBuilder,
cache, Stats */
/* globals RenderingStates, PDFJS, mozL10n, CustomStyle,
SCROLLBAR_PADDING, CSS_UNITS, UNKNOWN_SCALE, DEFAULT_SCALE,
getOutputScale, scrollIntoView, Stats, PresentationModeState */
'use strict';
var PageView = function pageView(container, id, scale,
navigateTo, defaultViewport) {
/**
* @constructor
* @param {HTMLDivElement} container - The viewer element.
* @param {number} id - The page unique ID (normally its number).
* @param {number} scale - The page scale display.
* @param {PageViewport} defaultViewport - The page viewport.
* @param {IPDFLinkService} linkService - The navigation/linking service.
* @param {PDFRenderingQueue} renderingQueue - The rendering queue object.
* @param {Cache} cache - The page cache.
* @param {PDFPageSource} pageSource
* @param {PDFViewer} viewer
*
* @implements {IRenderableView}
*/
var PageView = function pageView(container, id, scale, defaultViewport,
linkService, renderingQueue, cache,
pageSource, viewer) {
this.id = id;
this.renderingId = 'page' + id;
this.rotation = 0;
this.scale = scale || 1.0;
@ -31,6 +46,12 @@ var PageView = function pageView(container, id, scale,
this.pdfPageRotate = defaultViewport.rotation;
this.hasRestrictedScaling = false;
this.linkService = linkService;
this.renderingQueue = renderingQueue;
this.cache = cache;
this.pageSource = pageSource;
this.viewer = viewer;
this.renderingState = RenderingStates.INITIAL;
this.resume = null;
@ -241,10 +262,10 @@ var PageView = function pageView(container, id, scale,
function setupAnnotations(pageDiv, pdfPage, viewport) {
function bindLink(link, dest) {
link.href = PDFView.getDestinationHash(dest);
link.href = linkService.getDestinationHash(dest);
link.onclick = function pageViewSetupLinksOnclick() {
if (dest) {
PDFView.navigateTo(dest);
linkService.navigateTo(dest);
}
return false;
};
@ -254,47 +275,9 @@ var PageView = function pageView(container, id, scale,
}
function bindNamedAction(link, action) {
link.href = PDFView.getAnchorUrl('');
link.href = linkService.getAnchorUrl('');
link.onclick = function pageViewSetupNamedActionOnClick() {
// See PDF reference, table 8.45 - Named action
switch (action) {
case 'GoToPage':
document.getElementById('pageNumber').focus();
break;
case 'GoBack':
PDFHistory.back();
break;
case 'GoForward':
PDFHistory.forward();
break;
case 'Find':
if (!PDFView.supportsIntegratedFind) {
PDFView.findBar.toggle();
}
break;
case 'NextPage':
PDFView.page++;
break;
case 'PrevPage':
PDFView.page--;
break;
case 'LastPage':
PDFView.page = PDFView.pages.length;
break;
case 'FirstPage':
PDFView.page = 1;
break;
default:
break; // No action according to spec
}
linkService.executeNamedAction(action);
return false;
};
link.className = 'internalLink';
@ -376,14 +359,16 @@ var PageView = function pageView(container, id, scale,
};
this.scrollIntoView = function pageViewScrollIntoView(dest) {
if (PresentationMode.active) {
if (PDFView.page !== this.id) {
// Avoid breaking PDFView.getVisiblePages in presentation mode.
PDFView.page = this.id;
if (this.viewer.presentationModeState ===
PresentationModeState.FULLSCREEN) {
if (this.linkService.page !== this.id) {
// Avoid breaking getVisiblePages in presentation mode.
this.linkService.page = this.id;
return;
}
dest = null;
PDFView.setScale(PDFView.currentScaleValue, true, true);
// Fixes the case when PDF has different page sizes.
this.viewer.currentScaleValue = this.viewer.currentScaleValue;
}
if (!dest) {
scrollIntoView(div);
@ -431,9 +416,10 @@ var PageView = function pageView(container, id, scale,
y = dest[3];
width = dest[4] - x;
height = dest[5] - y;
widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) /
var viewerContainer = this.viewer.container;
widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
width / CSS_UNITS;
heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
height / CSS_UNITS;
scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
break;
@ -441,10 +427,10 @@ var PageView = function pageView(container, id, scale,
return;
}
if (scale && scale !== PDFView.currentScale) {
PDFView.setScale(scale, true, true);
} else if (PDFView.currentScale === UNKNOWN_SCALE) {
PDFView.setScale(DEFAULT_SCALE, true, true);
if (scale && scale !== this.viewer.currentScale) {
this.viewer.currentScaleValue = scale;
} else if (this.viewer.currentScale === UNKNOWN_SCALE) {
this.viewer.currentScaleValue = DEFAULT_SCALE;
}
if (scale === 'page-fit' && !dest[4]) {
@ -462,12 +448,6 @@ var PageView = function pageView(container, id, scale,
scrollIntoView(div, { left: left, top: top });
};
this.getTextContent = function pageviewGetTextContent() {
return PDFView.getPage(this.id).then(function(pdfPage) {
return pdfPage.getTextContent();
});
};
this.draw = function pageviewDraw(callback) {
var pdfPage = this.pdfPage;
@ -475,7 +455,7 @@ var PageView = function pageView(container, id, scale,
return;
}
if (!pdfPage) {
var promise = PDFView.getPage(this.id);
var promise = this.pageSource.getPage();
promise.then(function(pdfPage) {
delete this.pagePdfPromise;
this.setPdfPage(pdfPage);
@ -543,6 +523,7 @@ var PageView = function pageView(container, id, scale,
canvas._viewport = viewport;
var textLayerDiv = null;
var textLayer = null;
if (!PDFJS.disableTextLayer) {
textLayerDiv = document.createElement('div');
textLayerDiv.className = 'textLayer';
@ -554,16 +535,12 @@ var PageView = function pageView(container, id, scale,
} else {
div.appendChild(textLayerDiv);
}
textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1,
this.viewport);
}
var textLayer = this.textLayer =
textLayerDiv ? new TextLayerBuilder({
textLayerDiv: textLayerDiv,
pageIndex: this.id - 1,
lastScrollSource: PDFView,
viewport: this.viewport,
isViewerInPresentationMode: PresentationMode.active,
findController: PDFView.findController
}) : null;
this.textLayer = textLayer;
// TODO(mack): use data attributes to store these
ctx._scaleX = outputScale.sx;
ctx._scaleY = outputScale.sy;
@ -598,22 +575,7 @@ var PageView = function pageView(container, id, scale,
self.zoomLayer = null;
}
//#if (FIREFOX || MOZCENTRAL)
// if (self.textLayer && self.textLayer.textDivs &&
// self.textLayer.textDivs.length > 0 &&
// !PDFView.supportsDocumentColors) {
// console.error(mozL10n.get('document_colors_disabled', null,
// 'PDF documents are not allowed to use their own colors: ' +
// '\'Allow pages to choose their own colors\' ' +
// 'is deactivated in the browser.'));
// PDFView.fallback();
// }
//#endif
if (error) {
PDFView.error(mozL10n.get('rendering_error', null,
'An error occurred while rendering the page.'), error);
}
self.error = error;
self.stats = pdfPage.stats;
self.updateStats();
if (self.onAfterDraw) {
@ -626,18 +588,6 @@ var PageView = function pageView(container, id, scale,
});
div.dispatchEvent(event);
//#if (FIREFOX || MOZCENTRAL)
// FirefoxCom.request('reportTelemetry', JSON.stringify({
// type: 'pageInfo'
// }));
// // It is a good time to report stream and font types
// PDFView.pdfDocument.getStats().then(function (stats) {
// FirefoxCom.request('reportTelemetry', JSON.stringify({
// type: 'documentStats',
// stats: stats
// }));
// });
//#endif
callback();
}
@ -646,7 +596,7 @@ var PageView = function pageView(container, id, scale,
viewport: this.viewport,
// intent: 'default', // === 'display'
continueCallback: function pdfViewcContinueCallback(cont) {
if (PDFView.highestPriorityPage !== 'page' + self.id) {
if (!self.renderingQueue.isHighestPriority(self)) {
self.renderingState = RenderingStates.PAUSED;
self.resume = function resumeCallback() {
self.renderingState = RenderingStates.RUNNING;
@ -663,7 +613,7 @@ var PageView = function pageView(container, id, scale,
function pdfPageRenderCallback() {
pageViewDrawCallback(null);
if (textLayer) {
self.getTextContent().then(
self.pdfPage.getTextContent().then(
function textContentResolved(textContent) {
textLayer.setTextContent(textContent);
}

View File

@ -13,10 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFJS, FindStates, FirefoxCom, Promise */
/* globals PDFJS, FirefoxCom, Promise */
'use strict';
var FindStates = {
FIND_FOUND: 0,
FIND_NOTFOUND: 1,
FIND_WRAPPED: 2,
FIND_PENDING: 3
};
/**
* Provides "search" or "find" functionality for the PDF.
* This object actually performs the search for a given string.
@ -41,7 +48,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.state = null;
this.dirtyMatch = false;
this.findTimeout = null;
this.pdfPageSource = options.pdfPageSource || null;
this.pdfViewer = options.pdfViewer || null;
this.integratedFind = options.integratedFind || false;
this.charactersToNormalize = {
'\u2018': '\'', // Left single quotation mark
@ -137,7 +144,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.pageContents = [];
var extractTextPromisesResolves = [];
var numPages = this.pdfPageSource.pdfDocument.numPages;
var numPages = this.pdfViewer.pagesCount;
for (var i = 0; i < numPages; i++) {
this.extractTextPromises.push(new Promise(function (resolve) {
extractTextPromisesResolves.push(resolve);
@ -146,7 +153,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
var self = this;
function extractPageText(pageIndex) {
self.pdfPageSource.pages[pageIndex].getTextContent().then(
self.pdfViewer.getPageTextContent(pageIndex).then(
function textContentResolved(textContent) {
var textItems = textContent.items;
var str = [];
@ -159,7 +166,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
self.pageContents.push(str.join(''));
extractTextPromisesResolves[pageIndex](pageIndex);
if ((pageIndex + 1) < self.pdfPageSource.pages.length) {
if ((pageIndex + 1) < self.pdfViewer.pagesCount) {
extractPageText(pageIndex + 1);
}
}
@ -189,7 +196,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
},
updatePage: function PDFFindController_updatePage(index) {
var page = this.pdfPageSource.pages[index];
var page = this.pdfViewer.getPageView(index);
if (this.selected.pageIdx === index) {
// If the page is selected, scroll the page into view, which triggers
@ -205,8 +212,8 @@ var PDFFindController = (function PDFFindControllerClosure() {
nextMatch: function PDFFindController_nextMatch() {
var previous = this.state.findPrevious;
var currentPageIndex = this.pdfPageSource.page - 1;
var numPages = this.pdfPageSource.pages.length;
var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
var numPages = this.pdfViewer.pagesCount;
this.active = true;
@ -346,7 +353,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
this.updateUIState(state, this.state.findPrevious);
if (this.selected.pageIdx !== -1) {
this.updatePage(this.selected.pageIdx, true);
this.updatePage(this.selected.pageIdx);
}
},

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFJS, PDFView, PresentationMode */
/* globals PDFJS, PresentationMode */
'use strict';
@ -22,12 +22,11 @@ var PDFHistory = {
initialized: false,
initialDestination: null,
initialize: function pdfHistoryInitialize(fingerprint) {
if (PDFJS.disableHistory || PDFView.isViewerEmbedded) {
// The browsing history is only enabled when the viewer is standalone,
// i.e. not when it is embedded in a web page.
return;
}
/**
* @param {string} fingerprint
* @param {IPDFLinkService} linkService
*/
initialize: function pdfHistoryInitialize(fingerprint, linkService) {
this.initialized = true;
this.reInitialized = false;
this.allowHashChange = true;
@ -42,6 +41,7 @@ var PDFHistory = {
this.nextHashParam = '';
this.fingerprint = fingerprint;
this.linkService = linkService;
this.currentUid = this.uid = 0;
this.current = {};
@ -52,7 +52,7 @@ var PDFHistory = {
if (state.target.dest) {
this.initialDestination = state.target.dest;
} else {
PDFView.initialBookmark = state.target.hash;
linkService.setHash(state.target.hash);
}
this.currentUid = state.uid;
this.uid = state.uid + 1;
@ -203,7 +203,7 @@ var PDFHistory = {
params.hash = (this.current.hash && this.current.dest &&
this.current.dest === params.dest) ?
this.current.hash :
PDFView.getDestinationHash(params.dest).split('#')[1];
this.linkService.getDestinationHash(params.dest).split('#')[1];
}
if (params.page) {
params.page |= 0;
@ -212,7 +212,7 @@ var PDFHistory = {
var target = window.history.state.target;
if (!target) {
// Invoked when the user specifies an initial bookmark,
// thus setting PDFView.initialBookmark, when the document is loaded.
// thus setting initialBookmark, when the document is loaded.
this._pushToHistory(params, false);
this.previousHash = window.location.hash.substring(1);
}
@ -337,9 +337,9 @@ var PDFHistory = {
this.historyUnlocked = false;
if (state.target.dest) {
PDFView.navigateTo(state.target.dest);
this.linkService.navigateTo(state.target.dest);
} else {
PDFView.setHash(state.target.hash);
this.linkService.setHash(state.target.hash);
}
this.currentUid = state.uid;
if (state.uid > this.uid) {

176
web/pdf_rendering_queue.js Normal file
View File

@ -0,0 +1,176 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2012 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var CLEANUP_TIMEOUT = 30000;
var RenderingStates = {
INITIAL: 0,
RUNNING: 1,
PAUSED: 2,
FINISHED: 3
};
/**
* Controls rendering of the views for pages and thumbnails.
* @class
*/
var PDFRenderingQueue = (function PDFRenderingQueueClosure() {
/**
* @constructs
*/
function PDFRenderingQueue() {
this.pdfViewer = null;
this.pdfThumbnailViewer = null;
this.onIdle = null;
this.highestPriorityPage = null;
this.idleTimeout = null;
this.printing = false;
this.isThumbnailViewEnabled = false;
}
PDFRenderingQueue.prototype = /** @lends PDFRenderingQueue.prototype */ {
/**
* @param {PDFViewer} pdfViewer
*/
setViewer: function PDFRenderingQueue_setViewer(pdfViewer) {
this.pdfViewer = pdfViewer;
},
/**
* @param {PDFThumbnailViewer} pdfThumbnailViewer
*/
setThumbnailViewer:
function PDFRenderingQueue_setThumbnailViewer(pdfThumbnailViewer) {
this.pdfThumbnailViewer = pdfThumbnailViewer;
},
/**
* @param {IRenderableView} view
* @returns {boolean}
*/
isHighestPriority: function PDFRenderingQueue_isHighestPriority(view) {
return this.highestPriorityPage === view.renderingId;
},
renderHighestPriority: function
PDFRenderingQueue_renderHighestPriority(currentlyVisiblePages) {
if (this.idleTimeout) {
clearTimeout(this.idleTimeout);
this.idleTimeout = null;
}
// Pages have a higher priority than thumbnails, so check them first.
if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
return;
}
// No pages needed rendering so check thumbnails.
if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
if (this.pdfThumbnailViewer.forceRendering()) {
return;
}
}
if (this.printing) {
// If printing is currently ongoing do not reschedule cleanup.
return;
}
if (this.onIdle) {
this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
}
},
getHighestPriority: function
PDFRenderingQueue_getHighestPriority(visible, views, scrolledDown) {
// The state has changed figure out which page has the highest priority to
// render next (if any).
// Priority:
// 1 visible pages
// 2 if last scrolled down page after the visible pages
// 2 if last scrolled up page before the visible pages
var visibleViews = visible.views;
var numVisible = visibleViews.length;
if (numVisible === 0) {
return false;
}
for (var i = 0; i < numVisible; ++i) {
var view = visibleViews[i].view;
if (!this.isViewFinished(view)) {
return view;
}
}
// All the visible views have rendered, try to render next/previous pages.
if (scrolledDown) {
var nextPageIndex = visible.last.id;
// ID's start at 1 so no need to add 1.
if (views[nextPageIndex] &&
!this.isViewFinished(views[nextPageIndex])) {
return views[nextPageIndex];
}
} else {
var previousPageIndex = visible.first.id - 2;
if (views[previousPageIndex] &&
!this.isViewFinished(views[previousPageIndex])) {
return views[previousPageIndex];
}
}
// Everything that needs to be rendered has been.
return null;
},
/**
* @param {IRenderableView} view
* @returns {boolean}
*/
isViewFinished: function PDFRenderingQueue_isViewFinished(view) {
return view.renderingState === RenderingStates.FINISHED;
},
/**
* Render a page or thumbnail view. This calls the appropriate function
* based on the views state. If the view is already rendered it will return
* false.
* @param {IRenderableView} view
*/
renderView: function PDFRenderingQueue_renderView(view) {
var state = view.renderingState;
switch (state) {
case RenderingStates.FINISHED:
return false;
case RenderingStates.PAUSED:
this.highestPriorityPage = view.renderingId;
view.resume();
break;
case RenderingStates.RUNNING:
this.highestPriorityPage = view.renderingId;
break;
case RenderingStates.INITIAL:
this.highestPriorityPage = view.renderingId;
view.draw(this.renderHighestPriority.bind(this));
break;
}
return true;
},
};
return PDFRenderingQueue;
})();

571
web/pdf_viewer.js Normal file
View File

@ -0,0 +1,571 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* Copyright 2014 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PageView, UNKNOWN_SCALE,
SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS,
getVisibleElements, RenderingStates, Promise,
PDFJS, TextLayerBuilder, PDFRenderingQueue */
'use strict';
var PresentationModeState = {
UNKNOWN: 0,
NORMAL: 1,
CHANGING: 2,
FULLSCREEN: 3,
};
var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
//#include pdf_rendering_queue.js
//#include page_view.js
//#include text_layer_builder.js
/**
* @typedef {Object} PDFViewerOptions
* @property {HTMLDivElement} container - The container for the viewer element.
* @property {HTMLDivElement} viewer - (optional) The viewer element.
* @property {IPDFLinkService} linkService - The navigation/linking service.
* @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
* queue object.
*/
/**
* Simple viewer control to display PDF content/pages.
* @class
* @implements {ILastScrollSource}
* @implements {IRenderableView}
*/
var PDFViewer = (function pdfViewer() {
/**
* @constructs PDFViewer
* @param {PDFViewerOptions} options
*/
function PDFViewer(options) {
this.container = options.container;
this.viewer = options.viewer || options.container.firstElementChild;
this.linkService = options.linkService;
this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) {
// Custom rendering queue is not specified, using default one
this.renderingQueue = new PDFRenderingQueue();
this.renderingQueue.setViewer(this);
} else {
this.renderingQueue = options.renderingQueue;
}
this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
this.lastScroll = 0;
this.updateInProgress = false;
this.presentationModeState = PresentationModeState.UNKNOWN;
this._resetView();
}
PDFViewer.prototype = /** @lends PDFViewer.prototype */{
get pagesCount() {
return this.pages.length;
},
getPageView: function (index) {
return this.pages[index];
},
get currentPageNumber() {
return this._currentPageNumber;
},
set currentPageNumber(val) {
if (!this.pdfDocument) {
this._currentPageNumber = val;
return;
}
var event = document.createEvent('UIEvents');
event.initUIEvent('pagechange', true, true, window, 0);
event.updateInProgress = this.updateInProgress;
if (!(0 < val && val <= this.pagesCount)) {
event.pageNumber = this.page;
event.previousPageNumber = val;
this.container.dispatchEvent(event);
return;
}
this.pages[val - 1].updateStats();
event.previousPageNumber = this._currentPageNumber;
this._currentPageNumber = val;
event.pageNumber = val;
this.container.dispatchEvent(event);
},
/**
* @returns {number}
*/
get currentScale() {
return this._currentScale;
},
/**
* @param {number} val - Scale of the pages in percents.
*/
set currentScale(val) {
if (isNaN(val)) {
throw new Error('Invalid numeric scale');
}
if (!this.pdfDocument) {
this._currentScale = val;
this._currentScaleValue = val.toString();
return;
}
this._setScale(val, false);
},
/**
* @returns {string}
*/
get currentScaleValue() {
return this._currentScaleValue;
},
/**
* @param val - The scale of the pages (in percent or predefined value).
*/
set currentScaleValue(val) {
if (!this.pdfDocument) {
this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
this._currentScaleValue = val;
return;
}
this._setScale(val, false);
},
/**
* @returns {number}
*/
get pagesRotation() {
return this._pagesRotation;
},
/**
* @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
*/
set pagesRotation(rotation) {
this._pagesRotation = rotation;
for (var i = 0, l = this.pages.length; i < l; i++) {
var page = this.pages[i];
page.update(page.scale, rotation);
}
this._setScale(this._currentScaleValue, true);
},
/**
* @param pdfDocument {PDFDocument}
*/
setDocument: function (pdfDocument) {
if (this.pdfDocument) {
this._resetView();
}
this.pdfDocument = pdfDocument;
if (!pdfDocument) {
return;
}
var pagesCount = pdfDocument.numPages;
var pagesRefMap = this.pagesRefMap = {};
var self = this;
var resolvePagesPromise;
var pagesPromise = new Promise(function (resolve) {
resolvePagesPromise = resolve;
});
this.pagesPromise = pagesPromise;
pagesPromise.then(function () {
var event = document.createEvent('CustomEvent');
event.initCustomEvent('pagesloaded', true, true, {
pagesCount: pagesCount
});
self.container.dispatchEvent(event);
});
var isOnePageRenderedResolved = false;
var resolveOnePageRendered = null;
var onePageRendered = new Promise(function (resolve) {
resolveOnePageRendered = resolve;
});
this.onePageRendered = onePageRendered;
var bindOnAfterDraw = function (pageView) {
// when page is painted, using the image as thumbnail base
pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
if (!isOnePageRenderedResolved) {
isOnePageRenderedResolved = true;
resolveOnePageRendered();
}
var event = document.createEvent('CustomEvent');
event.initCustomEvent('pagerendered', true, true, {
pageNumber: pageView.id
});
self.container.dispatchEvent(event);
};
};
var firstPagePromise = pdfDocument.getPage(1);
this.firstPagePromise = firstPagePromise;
// Fetch a single page so we can get a viewport that will be the default
// viewport for all pages
return firstPagePromise.then(function(pdfPage) {
var scale = this._currentScale || 1.0;
var viewport = pdfPage.getViewport(scale * CSS_UNITS);
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
var pageSource = new PDFPageSource(pdfDocument, pageNum);
var pageView = new PageView(this.viewer, pageNum, scale,
viewport.clone(), this.linkService,
this.renderingQueue, this.cache,
pageSource, this);
bindOnAfterDraw(pageView);
this.pages.push(pageView);
}
// Fetch all the pages since the viewport is needed before printing
// starts to create the correct size canvas. Wait until one page is
// rendered so we don't tie up too many resources early on.
onePageRendered.then(function () {
if (!PDFJS.disableAutoFetch) {
var getPagesLeft = pagesCount;
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
var pageView = self.pages[pageNum - 1];
if (!pageView.pdfPage) {
pageView.setPdfPage(pdfPage);
}
var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
pagesRefMap[refStr] = pageNum;
getPagesLeft--;
if (!getPagesLeft) {
resolvePagesPromise();
}
}.bind(null, pageNum));
}
} else {
// XXX: Printing is semi-broken with auto fetch disabled.
resolvePagesPromise();
}
});
if (this.defaultRenderingQueue) {
firstPagePromise.then(this.update.bind(this));
}
}.bind(this));
},
_resetView: function () {
this.cache = new Cache(DEFAULT_CACHE_SIZE);
this.pages = [];
this._currentPageNumber = 1;
this._currentScale = UNKNOWN_SCALE;
this._currentScaleValue = null;
this.location = null;
this._pagesRotation = 0;
var container = this.viewer;
while (container.hasChildNodes()) {
container.removeChild(container.lastChild);
}
},
_scrollUpdate: function () {
this.lastScroll = Date.now();
if (this.pagesCount === 0) {
return;
}
this.update();
},
_setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
newScale, newValue, noScroll, preset) {
this._currentScaleValue = newValue;
if (newScale === this._currentScale) {
return;
}
for (var i = 0, ii = this.pages.length; i < ii; i++) {
this.pages[i].update(newScale);
}
this._currentScale = newScale;
if (!noScroll) {
var page = this._currentPageNumber, dest;
var inPresentationMode =
this.presentationModeState === PresentationModeState.CHANGING ||
this.presentationModeState === PresentationModeState.FULLSCREEN;
if (this.location && !inPresentationMode &&
!IGNORE_CURRENT_POSITION_ON_ZOOM) {
page = this.location.pageNumber;
dest = [null, { name: 'XYZ' }, this.location.left,
this.location.top, null];
}
this.pages[page - 1].scrollIntoView(dest);
}
var event = document.createEvent('UIEvents');
event.initUIEvent('scalechange', true, true, window, 0);
event.scale = newScale;
if (preset) {
event.presetValue = newValue;
}
this.container.dispatchEvent(event);
},
_setScale: function pdfViewer_setScale(value, noScroll) {
if (value === 'custom') {
return;
}
var scale = parseFloat(value);
if (scale > 0) {
this._setScaleUpdatePages(scale, value, noScroll, false);
} else {
var currentPage = this.pages[this._currentPageNumber - 1];
if (!currentPage) {
return;
}
var inPresentationMode =
this.presentationModeState === PresentationModeState.FULLSCREEN;
var hPadding = inPresentationMode ? 0 : SCROLLBAR_PADDING;
var vPadding = inPresentationMode ? 0 : VERTICAL_PADDING;
var pageWidthScale = (this.container.clientWidth - hPadding) /
currentPage.width * currentPage.scale;
var pageHeightScale = (this.container.clientHeight - vPadding) /
currentPage.height * currentPage.scale;
switch (value) {
case 'page-actual':
scale = 1;
break;
case 'page-width':
scale = pageWidthScale;
break;
case 'page-height':
scale = pageHeightScale;
break;
case 'page-fit':
scale = Math.min(pageWidthScale, pageHeightScale);
break;
case 'auto':
var isLandscape = (currentPage.width > currentPage.height);
var horizontalScale = isLandscape ? pageHeightScale :
pageWidthScale;
scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
break;
default:
console.error('pdfViewSetScale: \'' + value +
'\' is an unknown zoom value.');
return;
}
this._setScaleUpdatePages(scale, value, noScroll, true);
}
},
_updateLocation: function (firstPage) {
var currentScale = this._currentScale;
var currentScaleValue = this._currentScaleValue;
var normalizedScaleValue =
parseFloat(currentScaleValue) === currentScale ?
Math.round(currentScale * 10000) / 100 : currentScaleValue;
var pageNumber = firstPage.id;
var pdfOpenParams = '#page=' + pageNumber;
pdfOpenParams += '&zoom=' + normalizedScaleValue;
var currentPageView = this.pages[pageNumber - 1];
var container = this.container;
var topLeft = currentPageView.getPagePoint(
(container.scrollLeft - firstPage.x),
(container.scrollTop - firstPage.y));
var intLeft = Math.round(topLeft[0]);
var intTop = Math.round(topLeft[1]);
pdfOpenParams += ',' + intLeft + ',' + intTop;
this.location = {
pageNumber: pageNumber,
scale: normalizedScaleValue,
top: intTop,
left: intLeft,
pdfOpenParams: pdfOpenParams
};
},
update: function () {
var visible = this._getVisiblePages();
var visiblePages = visible.views;
if (visiblePages.length === 0) {
return;
}
this.updateInProgress = true;
var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
2 * visiblePages.length + 1);
this.cache.resize(suggestedCacheSize);
this.renderingQueue.renderHighestPriority(visible);
var currentId = this.currentPageNumber;
var firstPage = visible.first;
for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
i < ii; ++i) {
var page = visiblePages[i];
if (page.percent < 100) {
break;
}
if (page.id === currentId) {
stillFullyVisible = true;
break;
}
}
if (!stillFullyVisible) {
currentId = visiblePages[0].id;
}
if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
this.currentPageNumber = currentId;
}
this._updateLocation(firstPage);
this.updateInProgress = false;
var event = document.createEvent('UIEvents');
event.initUIEvent('updateviewarea', true, true, window, 0);
this.container.dispatchEvent(event);
},
containsElement: function (element) {
return this.container.contains(element);
},
focus: function () {
this.container.focus();
},
blur: function () {
this.container.blur();
},
get isHorizontalScrollbarEnabled() {
return (this.presentationModeState === PresentationModeState.FULLSCREEN ?
false : (this.container.scrollWidth > this.container.clientWidth));
},
_getVisiblePages: function () {
if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
return getVisibleElements(this.container, this.pages, true);
} else {
// The algorithm in getVisibleElements doesn't work in all browsers and
// configurations when presentation mode is active.
var visible = [];
var currentPage = this.pages[this._currentPageNumber - 1];
visible.push({ id: currentPage.id, view: currentPage });
return { first: currentPage, last: currentPage, views: visible };
}
},
cleanup: function () {
for (var i = 0, ii = this.pages.length; i < ii; i++) {
if (this.pages[i] &&
this.pages[i].renderingState !== RenderingStates.FINISHED) {
this.pages[i].reset();
}
}
},
forceRendering: function (currentlyVisiblePages) {
var visiblePages = currentlyVisiblePages || this._getVisiblePages();
var pageView = this.renderingQueue.getHighestPriority(visiblePages,
this.pages,
this.scroll.down);
if (pageView) {
this.renderingQueue.renderView(pageView);
return;
}
},
getPageTextContent: function (pageIndex) {
return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
return page.getTextContent();
});
},
/**
* @param textLayerDiv {HTMLDivElement}
* @param pageIndex {number}
* @param viewport {PageViewport}
* @returns {TextLayerBuilder}
*/
createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
var isViewerInPresentationMode =
this.presentationModeState === PresentationModeState.FULLSCREEN;
return new TextLayerBuilder({
textLayerDiv: textLayerDiv,
pageIndex: pageIndex,
viewport: viewport,
lastScrollSource: this,
isViewerInPresentationMode: isViewerInPresentationMode,
findController: this.findController
});
},
setFindController: function (findController) {
this.findController = findController;
},
};
return PDFViewer;
})();
/**
* PDFPage object source.
* @class
*/
var PDFPageSource = (function PDFPageSourceClosure() {
/**
* @constructs
* @param {PDFDocument} pdfDocument
* @param {number} pageNumber
* @constructor
*/
function PDFPageSource(pdfDocument, pageNumber) {
this.pdfDocument = pdfDocument;
this.pageNumber = pageNumber;
}
PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ {
/**
* @returns {Promise<PDFPage>}
*/
getPage: function () {
return this.pdfDocument.getPage(this.pageNumber);
}
};
return PDFPageSource;
})();

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFView, scrollIntoView, HandTool */
/* globals scrollIntoView, HandTool, PDFViewerApplication */
'use strict';
@ -68,7 +68,7 @@ var PresentationMode = {
},
/**
* Initialize a timeout that is used to reset PDFView.currentPosition when the
* Initialize a timeout that is used to specify switchInProgress when the
* browser transitions to fullscreen mode. Since resize events are triggered
* multiple times during the switch to fullscreen mode, this is necessary in
* order to prevent the page from being scrolled partially, or completely,
@ -81,9 +81,8 @@ var PresentationMode = {
}
this.switchInProgress = setTimeout(function switchInProgressTimeout() {
delete this.switchInProgress;
this._notifyStateChange();
}.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
PDFView.currentPosition = null;
},
_resetSwitchInProgress: function presentationMode_resetSwitchInProgress() {
@ -94,11 +93,12 @@ var PresentationMode = {
},
request: function presentationModeRequest() {
if (!PDFView.supportsFullscreen || this.isFullscreen ||
if (!PDFViewerApplication.supportsFullscreen || this.isFullscreen ||
!this.viewer.hasChildNodes()) {
return false;
}
this._setSwitchInProgress();
this._notifyStateChange();
if (this.container.requestFullscreen) {
this.container.requestFullscreen();
@ -113,23 +113,33 @@ var PresentationMode = {
}
this.args = {
page: PDFView.page,
previousScale: PDFView.currentScaleValue
page: PDFViewerApplication.page,
previousScale: PDFViewerApplication.currentScaleValue
};
return true;
},
_notifyStateChange: function presentationModeNotifyStateChange() {
var event = document.createEvent('CustomEvent');
event.initCustomEvent('presentationmodechanged', true, true, {
active: PresentationMode.active,
switchInProgress: !!PresentationMode.switchInProgress
});
window.dispatchEvent(event);
},
enter: function presentationModeEnter() {
this.active = true;
this._resetSwitchInProgress();
this._notifyStateChange();
// Ensure that the correct page is scrolled into view when entering
// Presentation Mode, by waiting until fullscreen mode in enabled.
// Note: This is only necessary in non-Mozilla browsers.
setTimeout(function enterPresentationModeTimeout() {
PDFView.page = this.args.page;
PDFView.setScale('page-fit', true);
PDFViewerApplication.page = this.args.page;
PDFViewerApplication.setScale('page-fit', true);
}.bind(this), 0);
window.addEventListener('mousemove', this.mouseMove, false);
@ -143,15 +153,17 @@ var PresentationMode = {
},
exit: function presentationModeExit() {
var page = PDFView.page;
var page = PDFViewerApplication.page;
// Ensure that the correct page is scrolled into view when exiting
// Presentation Mode, by waiting until fullscreen mode is disabled.
// Note: This is only necessary in non-Mozilla browsers.
setTimeout(function exitPresentationModeTimeout() {
this.active = false;
PDFView.setScale(this.args.previousScale);
PDFView.page = page;
this._notifyStateChange();
PDFViewerApplication.setScale(this.args.previousScale, true);
PDFViewerApplication.page = page;
this.args = null;
}.bind(this), 0);
@ -160,7 +172,7 @@ var PresentationMode = {
window.removeEventListener('contextmenu', this.contextMenu, false);
this.hideControls();
PDFView.clearMouseScrollState();
PDFViewerApplication.clearMouseScrollState();
HandTool.exitPresentationMode();
this.container.removeAttribute('contextmenu');
this.contextMenuOpen = false;
@ -224,7 +236,7 @@ var PresentationMode = {
if (!isInternalLink) {
// Unless an internal link was clicked, advance one page.
evt.preventDefault();
PDFView.page += (evt.shiftKey ? -1 : 1);
PDFViewerApplication.page += (evt.shiftKey ? -1 : 1);
}
}
},

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFView, SCROLLBAR_PADDING */
/* globals PDFViewerApplication, SCROLLBAR_PADDING */
'use strict';
@ -87,7 +87,7 @@ var SecondaryToolbar = {
},
downloadClick: function secondaryToolbarDownloadClick(evt) {
PDFView.download();
PDFViewerApplication.download();
this.close();
},
@ -96,23 +96,23 @@ var SecondaryToolbar = {
},
firstPageClick: function secondaryToolbarFirstPageClick(evt) {
PDFView.page = 1;
PDFViewerApplication.page = 1;
this.close();
},
lastPageClick: function secondaryToolbarLastPageClick(evt) {
if (PDFView.pdfDocument) {
PDFView.page = PDFView.pdfDocument.numPages;
if (PDFViewerApplication.pdfDocument) {
PDFViewerApplication.page = PDFViewerApplication.pagesCount;
}
this.close();
},
pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
PDFView.rotatePages(90);
PDFViewerApplication.rotatePages(90);
},
pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
PDFView.rotatePages(-90);
PDFViewerApplication.rotatePages(-90);
},
documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {

View File

@ -28,11 +28,23 @@ function isAllWhitespace(str) {
return !NonWhitespaceRegexp.test(str);
}
/**
* @typedef {Object} TextLayerBuilderOptions
* @property {HTMLDivElement} textLayerDiv - The text layer container.
* @property {number} pageIndex - The page index.
* @property {PageViewport} viewport - The viewport of the text layer.
* @property {ILastScrollSource} lastScrollSource - The object that records when
* last time scroll happened.
* @property {boolean} isViewerInPresentationMode
* @property {PDFFindController} findController
*/
/**
* TextLayerBuilder provides text-selection functionality for the PDF.
* It does this by creating overlay divs over the PDF text. These divs
* contain text that matches the PDF text they are overlaying. This object
* also provides a way to highlight text that is being searched for.
* @class
*/
var TextLayerBuilder = (function TextLayerBuilderClosure() {
function TextLayerBuilder(options) {

View File

@ -14,16 +14,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* globals PDFView, mozL10n, RenderingStates */
/* globals mozL10n, RenderingStates, Promise, scrollIntoView, PDFPageSource,
watchScroll, getVisibleElements */
'use strict';
var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
var THUMBNAIL_SCROLL_MARGIN = -19;
/**
* @constructor
* @param container
* @param id
* @param defaultViewport
* @param linkService
* @param renderingQueue
* @param pageSource
*
* @implements {IRenderableView}
*/
var ThumbnailView = function thumbnailView(container, id, defaultViewport,
linkService, renderingQueue,
pageSource) {
var anchor = document.createElement('a');
anchor.href = PDFView.getAnchorUrl('#page=' + id);
anchor.href = linkService.getAnchorUrl('#page=' + id);
anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
anchor.onclick = function stopNavigation() {
PDFView.page = id;
linkService.page = id;
return false;
};
@ -36,6 +52,7 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
this.pageHeight = this.viewport.height;
this.pageRatio = this.pageWidth / this.pageHeight;
this.id = id;
this.renderingId = 'thumbnail' + id;
this.canvasWidth = 98;
this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
@ -62,6 +79,8 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
this.hasImage = false;
this.renderingState = RenderingStates.INITIAL;
this.renderingQueue = renderingQueue;
this.pageSource = pageSource;
this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
this.pdfPage = pdfPage;
@ -125,7 +144,7 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
this.draw = function thumbnailViewDraw(callback) {
if (!this.pdfPage) {
var promise = PDFView.getPage(this.id);
var promise = this.pageSource.getPage(this.id);
promise.then(function(pdfPage) {
this.setPdfPage(pdfPage);
this.draw(callback);
@ -150,7 +169,7 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
canvasContext: ctx,
viewport: drawViewport,
continueCallback: function(cont) {
if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
if (!self.renderingQueue.isHighestPriority(self)) {
self.renderingState = RenderingStates.PAUSED;
self.resume = function() {
self.renderingState = RenderingStates.RUNNING;
@ -187,7 +206,7 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
this.setImage = function thumbnailViewSetImage(img) {
if (!this.pdfPage) {
var promise = PDFView.getPage(this.id);
var promise = this.pageSource.getPage();
promise.then(function(pdfPage) {
this.setPdfPage(pdfPage);
this.setImage(img);
@ -232,3 +251,134 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
};
ThumbnailView.tempImageCache = null;
/**
* @typedef {Object} PDFThumbnailViewerOptions
* @property {HTMLDivElement} container - The container for the thumbs elements.
* @property {IPDFLinkService} linkService - The navigation/linking service.
* @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
*/
/**
* Simple viewer control to display thumbs for pages.
* @class
*/
var PDFThumbnailViewer = (function pdfThumbnailViewer() {
/**
* @constructs
* @param {PDFThumbnailViewerOptions} options
*/
function PDFThumbnailViewer(options) {
this.container = options.container;
this.renderingQueue = options.renderingQueue;
this.linkService = options.linkService;
this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
this._resetView();
}
PDFThumbnailViewer.prototype = {
_scrollUpdated: function PDFThumbnailViewer_scrollUpdated() {
this.renderingQueue.renderHighestPriority();
},
getThumbnail: function PDFThumbnailViewer_getThumbnail(index) {
return this.thumbnails[index];
},
_getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() {
return getVisibleElements(this.container, this.thumbnails);
},
scrollThumbnailIntoView: function (page) {
var selected = document.querySelector('.thumbnail.selected');
if (selected) {
selected.classList.remove('selected');
}
var thumbnail = document.getElementById('thumbnailContainer' + page);
thumbnail.classList.add('selected');
var visibleThumbs = this._getVisibleThumbs();
var numVisibleThumbs = visibleThumbs.views.length;
// If the thumbnail isn't currently visible, scroll it into view.
if (numVisibleThumbs > 0) {
var first = visibleThumbs.first.id;
// Account for only one thumbnail being visible.
var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
if (page <= first || page >= last) {
scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
}
}
},
get pagesRotation() {
return this._pagesRotation;
},
set pagesRotation(rotation) {
this._pagesRotation = rotation;
for (var i = 0, l = this.thumbnails.length; i < l; i++) {
var thumb = this.thumbnails[i];
thumb.update(rotation);
}
},
cleanup: function PDFThumbnailViewer_cleanup() {
ThumbnailView.tempImageCache = null;
},
_resetView: function () {
this.thumbnails = [];
this._pagesRotation = 0;
},
setDocument: function (pdfDocument) {
if (this.pdfDocument) {
// cleanup of the elements and views
var thumbsView = this.container;
while (thumbsView.hasChildNodes()) {
thumbsView.removeChild(thumbsView.lastChild);
}
this._resetView();
}
this.pdfDocument = pdfDocument;
if (!pdfDocument) {
return Promise.resolve();
}
return pdfDocument.getPage(1).then(function (firstPage) {
var pagesCount = pdfDocument.numPages;
var viewport = firstPage.getViewport(1.0);
for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
var pageSource = new PDFPageSource(pdfDocument, pageNum);
var thumbnail = new ThumbnailView(this.container, pageNum,
viewport.clone(), this.linkService,
this.renderingQueue, pageSource);
this.thumbnails.push(thumbnail);
}
}.bind(this));
},
ensureThumbnailVisible:
function PDFThumbnailViewer_ensureThumbnailVisible(page) {
// Ensure that the thumbnail of the current page is visible
// when switching from another view.
scrollIntoView(document.getElementById('thumbnailContainer' + page));
},
forceRendering: function () {
var visibleThumbs = this._getVisibleThumbs();
var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs,
this.thumbnails,
this.scroll.down);
if (thumbView) {
this.renderingQueue.renderView(thumbView);
return true;
}
return false;
}
};
return PDFThumbnailViewer;
})();

View File

@ -16,6 +16,14 @@
'use strict';
var CSS_UNITS = 96.0 / 72.0;
var DEFAULT_SCALE = 'auto';
var UNKNOWN_SCALE = 0;
var MAX_AUTO_SCALE = 1.25;
var SCROLLBAR_PADDING = 40;
var VERTICAL_PADDING = 5;
var DEFAULT_CACHE_SIZE = 10;
// optimised CSS custom property getter/setter
var CustomStyle = (function CustomStyleClosure() {
@ -138,6 +146,91 @@ function scrollIntoView(element, spot) {
parent.scrollTop = offsetY;
}
/**
* Helper function to start monitoring the scroll event and converting them into
* PDF.js friendly one: with scroll debounce and scroll direction.
*/
function watchScroll(viewAreaElement, callback) {
var debounceScroll = function debounceScroll(evt) {
if (rAF) {
return;
}
// schedule an invocation of scroll for next animation frame.
rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
rAF = null;
var currentY = viewAreaElement.scrollTop;
var lastY = state.lastY;
if (currentY > lastY) {
state.down = true;
} else if (currentY < lastY) {
state.down = false;
}
state.lastY = currentY;
// else do nothing and use previous value
callback(state);
});
};
var state = {
down: true,
lastY: viewAreaElement.scrollTop,
_eventHandler: debounceScroll
};
var rAF = null;
viewAreaElement.addEventListener('scroll', debounceScroll, true);
return state;
}
/**
* Generic helper to find out what elements are visible within a scroll pane.
*/
function getVisibleElements(scrollEl, views, sortByVisibility) {
var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
var visible = [], view;
var currentHeight, viewHeight, hiddenHeight, percentHeight;
var currentWidth, viewWidth;
for (var i = 0, ii = views.length; i < ii; ++i) {
view = views[i];
currentHeight = view.el.offsetTop + view.el.clientTop;
viewHeight = view.el.clientHeight;
if ((currentHeight + viewHeight) < top) {
continue;
}
if (currentHeight > bottom) {
break;
}
currentWidth = view.el.offsetLeft + view.el.clientLeft;
viewWidth = view.el.clientWidth;
if ((currentWidth + viewWidth) < left || currentWidth > right) {
continue;
}
hiddenHeight = Math.max(0, top - currentHeight) +
Math.max(0, currentHeight + viewHeight - bottom);
percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
visible.push({ id: view.id, x: currentWidth, y: currentHeight,
view: view, percent: percentHeight });
}
var first = visible[0];
var last = visible[visible.length - 1];
if (sortByVisibility) {
visible.sort(function(a, b) {
var pc = a.percent - b.percent;
if (Math.abs(pc) > 0.001) {
return -pc;
}
return a.id - b.id; // ensure stability
});
}
return {first: first, last: last, views: visible};
}
/**
* Event handler to suppress context menu.
*/

View File

@ -68,9 +68,11 @@ http://sourceforge.net/adobe/cmap/wiki/License/
<script src="preferences.js"></script>
<script src="download_manager.js"></script>
<script src="view_history.js"></script>
<script src="pdf_rendering_queue.js"></script>
<script src="page_view.js"></script>
<script src="thumbnail_view.js"></script>
<script src="text_layer_builder.js"></script>
<script src="pdf_viewer.js"></script>
<script src="thumbnail_view.js"></script>
<script src="document_outline_view.js"></script>
<script src="document_attachments_view.js"></script>
<script src="pdf_find_bar.js"></script>

File diff suppressed because it is too large Load Diff