diff --git a/web/chromecom.js b/web/chromecom.js index f1739860b..5fe69fef2 100644 --- a/web/chromecom.js +++ b/web/chromecom.js @@ -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; diff --git a/web/document_attachments_view.js b/web/document_attachments_view.js index c8477dced..fd015040c 100644 --- a/web/document_attachments_view.js +++ b/web/document_attachments_view.js @@ -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; } diff --git a/web/document_outline_view.js b/web/document_outline_view.js index 74dbe3fb4..5a3ccd315 100644 --- a/web/document_outline_view.js +++ b/web/document_outline_view.js @@ -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; }; } diff --git a/web/document_properties.js b/web/document_properties.js index c718cc575..b8d30aa05 100644 --- a/web/document_properties.js +++ b/web/document_properties.js @@ -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. diff --git a/web/interfaces.js b/web/interfaces.js new file mode 100644 index 000000000..54c2825f0 --- /dev/null +++ b/web/interfaces.js @@ -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() {}, +}; diff --git a/web/page_view.js b/web/page_view.js index 62a90686d..46b53a6db 100644 --- a/web/page_view.js +++ b/web/page_view.js @@ -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); } diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js index 6459faf27..1b2dc6497 100644 --- a/web/pdf_find_controller.js +++ b/web/pdf_find_controller.js @@ -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); } }, diff --git a/web/pdf_history.js b/web/pdf_history.js index 79161da25..e15e03a66 100644 --- a/web/pdf_history.js +++ b/web/pdf_history.js @@ -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) { diff --git a/web/pdf_rendering_queue.js b/web/pdf_rendering_queue.js new file mode 100644 index 000000000..c7b1b3aba --- /dev/null +++ b/web/pdf_rendering_queue.js @@ -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; +})(); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js new file mode 100644 index 000000000..25c882586 --- /dev/null +++ b/web/pdf_viewer.js @@ -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} + */ + getPage: function () { + return this.pdfDocument.getPage(this.pageNumber); + } + }; + + return PDFPageSource; +})(); diff --git a/web/presentation_mode.js b/web/presentation_mode.js index 46a4fc2de..5d72e7c43 100644 --- a/web/presentation_mode.js +++ b/web/presentation_mode.js @@ -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); } } }, diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 7b6b5858c..7c94b3185 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -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) { diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index bc27bb81b..5fac1c493 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -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) { diff --git a/web/thumbnail_view.js b/web/thumbnail_view.js index c48926698..1e16a7de4 100644 --- a/web/thumbnail_view.js +++ b/web/thumbnail_view.js @@ -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; +})(); diff --git a/web/ui_utils.js b/web/ui_utils.js index b38ec4bc3..1779b60c3 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -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. */ diff --git a/web/viewer.html b/web/viewer.html index d15c3456d..471d0361a 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -68,9 +68,11 @@ http://sourceforge.net/adobe/cmap/wiki/License/ + - + + diff --git a/web/viewer.js b/web/viewer.js index 7cb31cc02..87416c8e3 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -17,46 +17,28 @@ /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar, DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory, Preferences, SidebarView, ViewHistory, PageView, - ThumbnailView, URL, noContextMenuHandler, SecondaryToolbar, + PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar, PasswordPrompt, PresentationMode, HandTool, Promise, DocumentProperties, DocumentOutlineView, DocumentAttachmentsView, - OverlayManager, PDFFindController, PDFFindBar */ + OverlayManager, PDFFindController, PDFFindBar, getVisibleElements, + watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState, + DEFAULT_SCALE, UNKNOWN_SCALE, + IGNORE_CURRENT_POSITION_ON_ZOOM: true */ 'use strict'; var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf'; -var DEFAULT_SCALE = 'auto'; var DEFAULT_SCALE_DELTA = 1.1; -var UNKNOWN_SCALE = 0; -var DEFAULT_CACHE_SIZE = 10; -var CSS_UNITS = 96.0 / 72.0; -var SCROLLBAR_PADDING = 40; -var VERTICAL_PADDING = 5; -var MAX_AUTO_SCALE = 1.25; var MIN_SCALE = 0.25; var MAX_SCALE = 10.0; var VIEW_HISTORY_MEMORY = 20; var SCALE_SELECT_CONTAINER_PADDING = 8; var SCALE_SELECT_PADDING = 22; -var THUMBNAIL_SCROLL_MARGIN = -19; -var CLEANUP_TIMEOUT = 30000; -var IGNORE_CURRENT_POSITION_ON_ZOOM = false; + //#if B2G //PDFJS.useOnlyCssZoom = true; //PDFJS.disableTextLayer = true; //#endif -var RenderingStates = { - INITIAL: 0, - RUNNING: 1, - PAUSED: 2, - FINISHED: 3 -}; -var FindStates = { - FIND_FOUND: 0, - FIND_NOTFOUND: 1, - FIND_WRAPPED: 2, - FIND_PENDING: 3 -}; PDFJS.imageResourcesPath = './images/'; //#if (FIREFOX || MOZCENTRAL || B2G || GENERIC || CHROME) @@ -91,9 +73,6 @@ var mozL10n = document.mozL10n || document.webL10n; //#include chromecom.js //#endif -var cache = new Cache(DEFAULT_CACHE_SIZE); -var currentPageNumber = 1; - //#include view_history.js //#include pdf_find_bar.js //#include pdf_find_controller.js @@ -104,51 +83,61 @@ var currentPageNumber = 1; //#include overlay_manager.js //#include password_prompt.js //#include document_properties.js +//#include pdf_viewer.js -var PDFView = { - pages: [], - thumbnails: [], - currentScale: UNKNOWN_SCALE, - currentScaleValue: null, +var PDFViewerApplication = { initialBookmark: document.location.hash.substring(1), - container: null, - thumbnailContainer: null, initialized: false, fellback: false, pdfDocument: null, sidebarOpen: false, printing: false, - pageViewScroll: null, - thumbnailViewScroll: null, + /** @type {PDFViewer} */ + pdfViewer: null, + /** @type {PDFThumbnailViewer} */ + pdfThumbnailViewer: null, + /** @type {PDFRenderingQueue} */ + pdfRenderingQueue: null, pageRotation: 0, + updateScaleControls: true, + isInitialViewSet: false, + animationStartedPromise: null, mouseScrollTimeStamp: 0, mouseScrollDelta: 0, - lastScroll: 0, - previousPageNumber: 1, isViewerEmbedded: (window.parent !== window), - idleTimeout: null, - currentPosition: null, url: '', // called once when the document is loaded initialize: function pdfViewInitialize() { - var self = this; - var container = this.container = document.getElementById('viewerContainer'); - this.pageViewScroll = {}; - this.watchScroll(container, this.pageViewScroll, updateViewarea); + var pdfRenderingQueue = new PDFRenderingQueue(); + pdfRenderingQueue.onIdle = this.cleanup.bind(this); + this.pdfRenderingQueue = pdfRenderingQueue; - var thumbnailContainer = this.thumbnailContainer = - document.getElementById('thumbnailView'); - this.thumbnailViewScroll = {}; - this.watchScroll(thumbnailContainer, this.thumbnailViewScroll, - this.renderHighestPriority.bind(this)); + var container = document.getElementById('viewerContainer'); + var viewer = document.getElementById('viewer'); + this.pdfViewer = new PDFViewer({ + container: container, + viewer: viewer, + renderingQueue: pdfRenderingQueue, + linkService: this + }); + pdfRenderingQueue.setViewer(this.pdfViewer); + + var thumbnailContainer = document.getElementById('thumbnailView'); + this.pdfThumbnailViewer = new PDFThumbnailViewer({ + container: thumbnailContainer, + renderingQueue: pdfRenderingQueue, + linkService: this + }); + pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer); Preferences.initialize(); this.findController = new PDFFindController({ - pdfPageSource: this, + pdfViewer: this.pdfViewer, integratedFind: this.supportsIntegratedFind }); + this.pdfViewer.setFindController(this.findController); this.findBar = new PDFFindBar({ bar: document.getElementById('findbar'), @@ -222,10 +211,6 @@ var PDFView = { pageCountField: document.getElementById('pageCountField') }); - container.addEventListener('scroll', function() { - self.lastScroll = Date.now(); - }, false); - var initializedPromise = Promise.all([ Preferences.get('enableWebGL').then(function resolved(value) { PDFJS.disableWebGL = !value; @@ -264,121 +249,16 @@ var PDFView = { ]).catch(function (reason) { }); return initializedPromise.then(function () { - PDFView.initialized = true; + PDFViewerApplication.initialized = true; }); }, - getPage: function pdfViewGetPage(n) { - return this.pdfDocument.getPage(n); - }, - - // Helper function to keep track whether a div was scrolled up or down and - // then call a callback. - watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) { - state.down = true; - state.lastY = viewAreaElement.scrollTop; - state.rAF = null; - viewAreaElement.addEventListener('scroll', function debounceScroll(evt) { - if (state.rAF) { - return; - } - // schedule an invocation of webViewerScrolled for next animation frame. - state.rAF = window.requestAnimationFrame(function webViewerScrolled() { - state.rAF = null; - if (!PDFView.pdfDocument) { - return; - } - var currentY = viewAreaElement.scrollTop; - var lastY = state.lastY; - if (currentY > lastY) { - state.down = true; - } else if (currentY < lastY) { - state.down = false; - } - // else do nothing and use previous value - state.lastY = currentY; - callback(); - }); - }, true); - }, - - _setScaleUpdatePages: function pdfView_setScaleUpdatePages( - newScale, newValue, resetAutoSettings, noScroll) { - 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.page, dest; - if (this.currentPosition && !IGNORE_CURRENT_POSITION_ON_ZOOM) { - page = this.currentPosition.page; - dest = [null, { name: 'XYZ' }, this.currentPosition.left, - this.currentPosition.top, null]; - } - this.pages[page - 1].scrollIntoView(dest); - } - var event = document.createEvent('UIEvents'); - event.initUIEvent('scalechange', false, false, window, 0); - event.scale = newScale; - event.resetAutoSettings = resetAutoSettings; - window.dispatchEvent(event); - }, - - setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) { - if (value === 'custom') { - return; - } - var scale = parseFloat(value); - - if (scale > 0) { - this._setScaleUpdatePages(scale, value, true, noScroll); - } else { - var currentPage = this.pages[this.page - 1]; - if (!currentPage) { - return; - } - var hPadding = PresentationMode.active ? 0 : SCROLLBAR_PADDING; - var vPadding = PresentationMode.active ? 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, resetAutoSettings, noScroll); - - selectScaleOption(value); - } + getPageView: function pdfViewGetPageView(index) { + return this.pdfViewer.pages[index]; }, zoomIn: function pdfViewZoomIn(ticks) { - var newScale = this.currentScale; + var newScale = this.pdfViewer.currentScale; do { newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2); newScale = Math.ceil(newScale * 10) / 10; @@ -388,7 +268,7 @@ var PDFView = { }, zoomOut: function pdfViewZoomOut(ticks) { - var newScale = this.currentScale; + var newScale = this.pdfViewer.currentScale; do { newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2); newScale = Math.floor(newScale * 10) / 10; @@ -397,38 +277,20 @@ var PDFView = { this.setScale(newScale, true); }, + get currentScaleValue() { + return this.pdfViewer.currentScaleValue; + }, + + get pagesCount() { + return this.pdfDocument.numPages; + }, + set page(val) { - var pages = this.pages; - var event = document.createEvent('UIEvents'); - event.initUIEvent('pagechange', false, false, window, 0); - - if (!(0 < val && val <= pages.length)) { - this.previousPageNumber = val; - event.pageNumber = this.page; - window.dispatchEvent(event); - return; - } - - pages[val - 1].updateStats(); - this.previousPageNumber = currentPageNumber; - currentPageNumber = val; - event.pageNumber = val; - window.dispatchEvent(event); - - // checking if the this.page was called from the updateViewarea function: - // avoiding the creation of two "set page" method (internal and public) - if (updateViewarea.inProgress) { - return; - } - // Avoid scrolling the first page during loading - if (this.loading && val === 1) { - return; - } - pages[val - 1].scrollIntoView(); + this.pdfViewer.currentPageNumber = val; }, get page() { - return currentPageNumber; + return this.pdfViewer.currentPageNumber; }, get supportsPrinting() { @@ -509,11 +371,6 @@ var PDFView = { return bar; }, - get isHorizontalScrollbarEnabled() { - return (PresentationMode.active ? false : - (this.container.scrollWidth > this.container.clientWidth)); - }, - //#if (FIREFOX || MOZCENTRAL) initPassiveLoading: function pdfViewInitPassiveLoading() { var pdfDataRangeTransportReadyResolve; @@ -590,7 +447,8 @@ var PDFView = { } switch (args.pdfjsLoadAction) { case 'supportsRangedLoading': - PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, { + PDFViewerApplication.open(args.pdfUrl, 0, undefined, + pdfDataRangeTransport, { length: args.length, initialData: args.data }); @@ -605,15 +463,15 @@ var PDFView = { pdfDataRangeTransport.onDataProgressiveRead(args.chunk); break; case 'progress': - PDFView.progress(args.loaded / args.total); + PDFViewerApplication.progress(args.loaded / args.total); break; case 'complete': if (!args.data) { - PDFView.error(mozL10n.get('loading_error', null, - 'An error occurred while loading the PDF.'), e); + PDFViewerApplication.error(mozL10n.get('loading_error', null, + 'An error occurred while loading the PDF.'), e); break; } - PDFView.open(args.data, 0); + PDFViewerApplication.open(args.data, 0); break; } }); @@ -650,15 +508,8 @@ var PDFView = { this.pdfDocument.destroy(); this.pdfDocument = null; - var thumbsView = document.getElementById('thumbnailView'); - while (thumbsView.hasChildNodes()) { - thumbsView.removeChild(thumbsView.lastChild); - } - - var container = document.getElementById('viewer'); - while (container.hasChildNodes()) { - container.removeChild(container.lastChild); - } + this.pdfThumbnailViewer.setDocument(null); + this.pdfViewer.setDocument(null); if (typeof PDFBug !== 'undefined') { PDFBug.cleanup(); @@ -756,7 +607,7 @@ var PDFView = { downloadManager.onerror = function (err) { // This error won't really be helpful because it's likely the // fallback won't work either (or is already open). - PDFView.error('PDF failed to download.'); + PDFViewerApplication.error('PDF failed to download.'); }; if (!this.pdfDocument) { // the PDF is not ready yet @@ -793,7 +644,7 @@ var PDFView = { // if (!download) { // return; // } -// PDFView.download(); +// PDFViewerApplication.download(); // }); //#endif }, @@ -809,10 +660,10 @@ var PDFView = { self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); if (pageNumber) { - if (pageNumber > self.pages.length) { - pageNumber = self.pages.length; + if (pageNumber > self.pagesCount) { + pageNumber = self.pagesCount; } - var currentPage = self.pages[pageNumber - 1]; + var currentPage = self.getPageView(pageNumber - 1); currentPage.scrollIntoView(dest); // Update the browsing history. @@ -838,9 +689,51 @@ var PDFView = { }); }, + executeNamedAction: function pdfViewExecuteNamedAction(action) { + // 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 (!this.supportsIntegratedFind) { + this.findBar.toggle(); + } + break; + + case 'NextPage': + this.page++; + break; + + case 'PrevPage': + this.page--; + break; + + case 'LastPage': + this.page = this.pagesCount; + break; + + case 'FirstPage': + this.page = 1; + break; + + default: + break; // No action according to spec + } + }, + getDestinationHash: function pdfViewGetDestinationHash(dest) { if (typeof dest === 'string') { - return PDFView.getAnchorUrl('#' + escape(dest)); + return this.getAnchorUrl('#' + escape(dest)); } if (dest instanceof Array) { var destRef = dest[0]; // see navigateTo method for dest format @@ -848,7 +741,7 @@ var PDFView = { this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1); if (pageNumber) { - var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber); + var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber); var destKind = dest[1]; if (typeof destKind === 'object' && 'name' in destKind && destKind.name === 'XYZ') { @@ -962,38 +855,26 @@ var PDFView = { // that we discard some of the loaded data. This can cause the loading // bar to move backwards. So prevent this by only updating the bar if it // increases. - if (percent > PDFView.loadingBar.percent || isNaN(percent)) { - PDFView.loadingBar.percent = percent; + if (percent > this.loadingBar.percent || isNaN(percent)) { + this.loadingBar.percent = percent; } }, load: function pdfViewLoad(pdfDocument, scale) { var self = this; - var isOnePageRenderedResolved = false; - var resolveOnePageRendered = null; - var onePageRendered = new Promise(function (resolve) { - resolveOnePageRendered = resolve; - }); - function bindOnAfterDraw(pageView, thumbnailView) { - // when page is painted, using the image as thumbnail base - pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() { - if (!isOnePageRenderedResolved) { - isOnePageRenderedResolved = true; - resolveOnePageRendered(); - } - thumbnailView.setImage(pageView.canvas); - }; - } + scale = scale || UNKNOWN_SCALE; - PDFView.findController.reset(); + this.findController.reset(); this.pdfDocument = pdfDocument; + DocumentProperties.url = this.url; + DocumentProperties.pdfDocument = pdfDocument; DocumentProperties.resolveDataAvailable(); var downloadedPromise = pdfDocument.getDownloadInfo().then(function() { self.downloadComplete = true; - PDFView.loadingBar.hide(); + self.loadingBar.hide(); var outerContainer = document.getElementById('outerContainer'); outerContainer.classList.remove('loadingInProgress'); }); @@ -1005,79 +886,38 @@ var PDFView = { mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}'); document.getElementById('pageNumber').max = pagesCount; - PDFView.documentFingerprint = id; - var store = PDFView.store = new ViewHistory(id); + this.documentFingerprint = id; + var store = this.store = new ViewHistory(id); + + var pdfViewer = this.pdfViewer; + pdfViewer.currentScale = scale; + pdfViewer.setDocument(pdfDocument); + var firstPagePromise = pdfViewer.firstPagePromise; + var pagesPromise = pdfViewer.pagesPromise; + var onePageRendered = pdfViewer.onePageRendered; this.pageRotation = 0; + this.isInitialViewSet = false; + this.pagesRefMap = pdfViewer.pagesRefMap; - var pages = this.pages = []; - var pagesRefMap = this.pagesRefMap = {}; - var thumbnails = this.thumbnails = []; + this.pdfThumbnailViewer.setDocument(pdfDocument); - var resolvePagesPromise; - var pagesPromise = new Promise(function (resolve) { - resolvePagesPromise = resolve; - }); - this.pagesPromise = pagesPromise; - - var firstPagePromise = pdfDocument.getPage(1); - var container = document.getElementById('viewer'); - var thumbsView = document.getElementById('thumbnailView'); - - // Fetch a single page so we can get a viewport that will be the default - // viewport for all pages firstPagePromise.then(function(pdfPage) { - var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS); - for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { - var viewportClone = viewport.clone(); - var pageView = new PageView(container, pageNum, scale, - self.navigateTo.bind(self), - viewportClone); - var thumbnailView = new ThumbnailView(thumbsView, pageNum, - viewportClone); - bindOnAfterDraw(pageView, thumbnailView); - pages.push(pageView); - thumbnails.push(thumbnailView); - } - - // 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 = 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(); - } - }); - downloadedPromise.then(function () { var event = document.createEvent('CustomEvent'); event.initCustomEvent('documentload', true, true, {}); window.dispatchEvent(event); }); - PDFView.loadingBar.setWidth(container); + self.loadingBar.setWidth(document.getElementById('viewer')); - PDFView.findController.resolveFirstPage(); + self.findController.resolveFirstPage(); - // Initialize the browsing history. - PDFHistory.initialize(self.documentFingerprint); + if (!PDFJS.disableHistory && !self.isViewerEmbedded) { + // The browsing history is only enabled when the viewer is standalone, + // i.e. not when it is embedded in a web page. + PDFHistory.initialize(self.documentFingerprint, self); + } }); // Fetch the necessary preference values. @@ -1098,7 +938,8 @@ var PDFView = { var storedHash = null; if (showPreviousViewOnLoad && store.get('exists', false)) { var pageNum = store.get('page', '1'); - var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale); + var zoom = defaultZoomValue || + store.get('zoom', self.pdfViewer.currentScale); var left = store.get('scrollLeft', '0'); var top = store.get('scrollTop', '0'); @@ -1112,9 +953,9 @@ var PDFView = { // Make all navigation keys work on document load, // unless the viewer is embedded in a web page. if (!self.isViewerEmbedded) { - self.container.focus(); + self.pdfViewer.focus(); //#if (FIREFOX || MOZCENTRAL) -// self.container.blur(); +// self.pdfViewer.blur(); //#endif } }, function rejected(reason) { @@ -1126,11 +967,11 @@ var PDFView = { }); pagesPromise.then(function() { - if (PDFView.supportsPrinting) { + if (self.supportsPrinting) { pdfDocument.getJavaScript().then(function(javaScript) { if (javaScript.length) { console.warn('Warning: JavaScript is not supported'); - PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript); + self.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript); } // Hack to support auto printing. var regex = /\bprint\s*\(/g; @@ -1155,21 +996,36 @@ var PDFView = { // outline depends on destinations and pagesRefMap var promises = [pagesPromise, destinationsPromise, - PDFView.animationStartedPromise]; + this.animationStartedPromise]; Promise.all(promises).then(function() { pdfDocument.getOutline().then(function(outline) { - self.outline = new DocumentOutlineView(outline); + var outlineView = document.getElementById('outlineView'); + self.outline = new DocumentOutlineView({ + outline: outline, + outlineView: outlineView, + linkService: self + }); document.getElementById('viewOutline').disabled = !outline; + if (!outline && !outlineView.classList.contains('hidden')) { + self.switchSidebarView('thumbs'); + } if (outline && self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) { self.switchSidebarView('outline', true); } }); pdfDocument.getAttachments().then(function(attachments) { - self.attachments = new DocumentAttachmentsView(attachments); + var attachmentsView = document.getElementById('attachmentsView'); + self.attachments = new DocumentAttachmentsView({ + attachments: attachments, + attachmentsView: attachmentsView + }); document.getElementById('viewAttachments').disabled = !attachments; + if (!attachments && !attachmentsView.classList.contains('hidden')) { + self.switchSidebarView('thumbs'); + } if (attachments && self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) { self.switchSidebarView('attachments', true); @@ -1214,7 +1070,7 @@ var PDFView = { if (info.IsAcroFormPresent) { console.warn('Warning: AcroForm/XFA is not supported'); - PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms); + self.fallback(PDFJS.UNSUPPORTED_FEATURES.forms); } //#if (FIREFOX || MOZCENTRAL) @@ -1248,17 +1104,13 @@ var PDFView = { }, setInitialView: function pdfViewSetInitialView(storedHash, scale) { - // Reset the current scale, as otherwise the page's scale might not get - // updated if the zoom level stayed the same. - this.currentScale = UNKNOWN_SCALE; - this.currentScaleValue = null; + this.isInitialViewSet = true; + // When opening a new file (when one is already loaded in the viewer): // Reset 'currentPageNumber', since otherwise the page's scale will be wrong // if 'currentPageNumber' is larger than the number of pages in the file. - document.getElementById('pageNumber').value = currentPageNumber = 1; - // Reset the current position when loading a new file, - // to prevent displaying the wrong position in the document. - this.currentPosition = null; + document.getElementById('pageNumber').value = + this.pdfViewer.currentPageNumber = 1; if (PDFHistory.initialDestination) { this.navigateTo(PDFHistory.initialDestination); @@ -1274,128 +1126,31 @@ var PDFView = { this.page = 1; } - if (PDFView.currentScale === UNKNOWN_SCALE) { + if (this.pdfViewer.currentScale === UNKNOWN_SCALE) { // Scale was not initialized: invalid bookmark or scale was not specified. // Setting the default one. this.setScale(DEFAULT_SCALE, true); } }, - renderHighestPriority: - function pdfViewRenderHighestPriority(currentlyVisiblePages) { - if (PDFView.idleTimeout) { - clearTimeout(PDFView.idleTimeout); - PDFView.idleTimeout = null; - } - - // Pages have a higher priority than thumbnails, so check them first. - var visiblePages = currentlyVisiblePages || this.getVisiblePages(); - var pageView = this.getHighestPriority(visiblePages, this.pages, - this.pageViewScroll.down); - if (pageView) { - this.renderView(pageView, 'page'); - return; - } - // No pages needed rendering so check thumbnails. - if (this.sidebarOpen) { - var visibleThumbs = this.getVisibleThumbs(); - var thumbView = this.getHighestPriority(visibleThumbs, - this.thumbnails, - this.thumbnailViewScroll.down); - if (thumbView) { - this.renderView(thumbView, 'thumbnail'); - return; - } - } - - if (this.printing) { - // If printing is currently ongoing do not reschedule cleanup. - return; - } - - PDFView.idleTimeout = setTimeout(function () { - PDFView.cleanup(); - }, CLEANUP_TIMEOUT); - }, - cleanup: function pdfViewCleanup() { - 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(); - } - } + this.pdfViewer.cleanup(); + this.pdfThumbnailViewer.cleanup(); this.pdfDocument.cleanup(); - - ThumbnailView.tempImageCache = null; }, - getHighestPriority: function pdfViewGetHighestPriority(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 false; - }, - - isViewFinished: function pdfViewIsViewFinished(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. - renderView: function pdfViewRender(view, type) { - var state = view.renderingState; - switch (state) { - case RenderingStates.FINISHED: - return false; - case RenderingStates.PAUSED: - PDFView.highestPriorityPage = type + view.id; - view.resume(); - break; - case RenderingStates.RUNNING: - PDFView.highestPriorityPage = type + view.id; - break; - case RenderingStates.INITIAL: - PDFView.highestPriorityPage = type + view.id; - view.draw(this.renderHighestPriority.bind(this)); - break; - } - return true; + forceRendering: function pdfViewForceRendering() { + this.pdfRenderingQueue.printing = this.printing; + this.pdfRenderingQueue.isThumbnailViewEnabled = this.sidebarOpen; + this.pdfRenderingQueue.renderHighestPriority(); }, setHash: function pdfViewSetHash(hash) { + if (!this.isInitialViewSet) { + this.initialBookmark = hash; + return; + } + var validFitZoomValues = ['Fit','FitB','FitH','FitBH', 'FitV','FitBV','FitR']; @@ -1404,11 +1159,11 @@ var PDFView = { } if (hash.indexOf('=') >= 0) { - var params = PDFView.parseQueryString(hash); + var params = this.parseQueryString(hash); // borrowing syntax from "Parameters for Opening PDF Files" if ('nameddest' in params) { PDFHistory.updateNextHashParam(params.nameddest); - PDFView.navigateTo(params.nameddest); + this.navigateTo(params.nameddest); return; } var pageNumber, dest; @@ -1435,7 +1190,7 @@ var PDFView = { zoomArg]; } if (dest) { - var currentPage = this.pages[(pageNumber || this.page) - 1]; + var currentPage = this.getPageView((pageNumber || this.page) - 1); currentPage.scrollIntoView(dest); } else if (pageNumber) { this.page = pageNumber; // simple page @@ -1453,7 +1208,7 @@ var PDFView = { this.page = hash; } else { // named destination PDFHistory.updateNextHashParam(unescape(hash)); - PDFView.navigateTo(unescape(hash)); + this.navigateTo(unescape(hash)); } }, @@ -1480,13 +1235,10 @@ var PDFView = { outlineView.classList.add('hidden'); attachmentsView.classList.add('hidden'); - PDFView.renderHighestPriority(); + this.forceRendering(); if (wasAnotherViewVisible) { - // Ensure that the thumbnail of the current page is visible - // when switching from another view. - scrollIntoView(document.getElementById('thumbnailContainer' + - this.page)); + this.pdfThumbnailViewer.ensureThumbnailVisible(this.page); } break; @@ -1518,70 +1270,6 @@ var PDFView = { } }, - getVisiblePages: function pdfViewGetVisiblePages() { - if (!PresentationMode.active) { - return this.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.page - 1]; - visible.push({ id: currentPage.id, view: currentPage }); - return { first: currentPage, last: currentPage, views: visible }; - } - }, - - getVisibleThumbs: function pdfViewGetVisibleThumbs() { - return this.getVisibleElements(this.thumbnailContainer, this.thumbnails); - }, - - // Generic helper to find out what elements are visible within a scroll pane. - getVisibleElements: function pdfViewGetVisibleElements( - 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}; - }, - // Helper function to parse query string (e.g. ?param1=value&parm2=...). parseQueryString: function pdfViewParseQueryString(query) { var parts = query.split('&'); @@ -1605,11 +1293,11 @@ var PDFView = { var alertNotReady = false; var i, ii; - if (!this.pages.length) { + if (!this.pagesCount) { alertNotReady = true; } else { - for (i = 0, ii = this.pages.length; i < ii; ++i) { - if (!this.pages[i].pdfPage) { + for (i = 0, ii = this.pagesCount; i < ii; ++i) { + if (!this.getPageView(i).pdfPage) { alertNotReady = true; break; } @@ -1623,12 +1311,12 @@ var PDFView = { } this.printing = true; - this.renderHighestPriority(); + this.forceRendering(); var body = document.querySelector('body'); body.setAttribute('data-mozPrintCallback', true); - for (i = 0, ii = this.pages.length; i < ii; ++i) { - this.pages[i].beforePrint(); + for (i = 0, ii = this.pagesCount; i < ii; ++i) { + this.getPageView(i).beforePrint(); } //#if (FIREFOX || MOZCENTRAL) @@ -1645,27 +1333,23 @@ var PDFView = { } this.printing = false; - this.renderHighestPriority(); + this.forceRendering(); + }, + + setScale: function (value, resetAutoSettings) { + this.updateScaleControls = !!resetAutoSettings; + this.pdfViewer.currentScaleValue = value; + this.updateScaleControls = true; }, rotatePages: function pdfViewRotatePages(delta) { - var currentPage = this.pages[this.page - 1]; - var i, l; + var currentPage = this.getPageView(this.page - 1); + this.pageRotation = (this.pageRotation + 360 + delta) % 360; + this.pdfViewer.pagesRotation = this.pageRotation; + this.pdfThumbnailViewer.pagesRotation = this.pageRotation; - for (i = 0, l = this.pages.length; i < l; i++) { - var page = this.pages[i]; - page.update(page.scale, this.pageRotation); - } - - for (i = 0, l = this.thumbnails.length; i < l; i++) { - var thumb = this.thumbnails[i]; - thumb.update(this.pageRotation); - } - - this.setScale(this.currentScaleValue, true, true); - - this.renderHighestPriority(); + this.forceRendering(); if (currentPage) { currentPage.scrollIntoView(); @@ -1719,7 +1403,7 @@ var PDFView = { // In case we are already on the first or the last page there is no need // to do anything. if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) || - (currentPage === this.pages.length && + (currentPage === this.pagesCount && pageFlipDirection === PageFlipDirection.DOWN)) { return; } @@ -1740,10 +1424,11 @@ var PDFView = { this.mouseScrollDelta = 0; } }; +//#if GENERIC +window.PDFView = PDFViewerApplication; // obsolete name, using it as an alias +//#endif -//#include page_view.js //#include thumbnail_view.js -//#include text_layer_builder.js //#include document_outline_view.js //#include document_attachments_view.js @@ -1751,7 +1436,8 @@ var PDFView = { //(function rewriteUrlClosure() { // // Run this code outside DOMContentLoaded to make sure that the URL // // is rewritten as soon as possible. -// var params = PDFView.parseQueryString(document.location.search.slice(1)); +// var queryString = document.location.search.slice(1); +// var params = PDFViewerApplication.parseQueryString(queryString); // DEFAULT_URL = params.file || ''; // // // Example: chrome-extension://.../http://example.com/file.pdf @@ -1764,12 +1450,13 @@ var PDFView = { //#endif function webViewerLoad(evt) { - PDFView.initialize().then(webViewerInitialized); + PDFViewerApplication.initialize().then(webViewerInitialized); } function webViewerInitialized() { //#if (GENERIC || B2G) - var params = PDFView.parseQueryString(document.location.search.substring(1)); + var queryString = document.location.search.substring(1); + var params = PDFViewerApplication.parseQueryString(queryString); var file = 'file' in params ? params.file : DEFAULT_URL; //#endif //#if (FIREFOX || MOZCENTRAL) @@ -1805,11 +1492,11 @@ function webViewerInitialized() { //#if !PRODUCTION if (true) { //#else -//if (PDFView.preferencesPdfBugEnabled) { +//if (PDFViewerApplication.preferencesPdfBugEnabled) { //#endif // Special debugging flags in the hash section of the URL. var hash = document.location.hash.substring(1); - var hashParams = PDFView.parseQueryString(hash); + var hashParams = PDFViewerApplication.parseQueryString(hash); if ('disableworker' in hashParams) { PDFJS.disableWorker = (hashParams['disableworker'] === 'true'); @@ -1879,30 +1566,31 @@ function webViewerInitialized() { mozL10n.setLanguage(locale); //#endif //#if (FIREFOX || MOZCENTRAL) -//if (!PDFView.supportsDocumentFonts) { +//if (!PDFViewerApplication.supportsDocumentFonts) { // PDFJS.disableFontFace = true; // console.warn(mozL10n.get('web_fonts_disabled', null, // 'Web fonts are disabled: unable to use embedded PDF fonts.')); //} //#endif - if (!PDFView.supportsPrinting) { + if (!PDFViewerApplication.supportsPrinting) { document.getElementById('print').classList.add('hidden'); document.getElementById('secondaryPrint').classList.add('hidden'); } - if (!PDFView.supportsFullscreen) { + if (!PDFViewerApplication.supportsFullscreen) { document.getElementById('presentationMode').classList.add('hidden'); document.getElementById('secondaryPresentationMode'). classList.add('hidden'); } - if (PDFView.supportsIntegratedFind) { + if (PDFViewerApplication.supportsIntegratedFind) { document.getElementById('viewFind').classList.add('hidden'); } // Listen for unsupported features to trigger the fallback UI. - PDFJS.UnsupportedManager.listen(PDFView.fallback.bind(PDFView)); + PDFJS.UnsupportedManager.listen( + PDFViewerApplication.fallback.bind(PDFViewerApplication)); // Suppress context menus for some controls document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler; @@ -1923,43 +1611,44 @@ function webViewerInitialized() { this.classList.toggle('toggled'); outerContainer.classList.add('sidebarMoving'); outerContainer.classList.toggle('sidebarOpen'); - PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen'); - PDFView.renderHighestPriority(); + PDFViewerApplication.sidebarOpen = + outerContainer.classList.contains('sidebarOpen'); + PDFViewerApplication.forceRendering(); }); document.getElementById('viewThumbnail').addEventListener('click', function() { - PDFView.switchSidebarView('thumbs'); + PDFViewerApplication.switchSidebarView('thumbs'); }); document.getElementById('viewOutline').addEventListener('click', function() { - PDFView.switchSidebarView('outline'); + PDFViewerApplication.switchSidebarView('outline'); }); document.getElementById('viewAttachments').addEventListener('click', function() { - PDFView.switchSidebarView('attachments'); + PDFViewerApplication.switchSidebarView('attachments'); }); document.getElementById('previous').addEventListener('click', function() { - PDFView.page--; + PDFViewerApplication.page--; }); document.getElementById('next').addEventListener('click', function() { - PDFView.page++; + PDFViewerApplication.page++; }); document.getElementById('zoomIn').addEventListener('click', function() { - PDFView.zoomIn(); + PDFViewerApplication.zoomIn(); }); document.getElementById('zoomOut').addEventListener('click', function() { - PDFView.zoomOut(); + PDFViewerApplication.zoomOut(); }); document.getElementById('pageNumber').addEventListener('click', @@ -1970,16 +1659,16 @@ function webViewerInitialized() { document.getElementById('pageNumber').addEventListener('change', function() { // Handle the user inputting a floating point number. - PDFView.page = (this.value | 0); + PDFViewerApplication.page = (this.value | 0); if (this.value !== (this.value | 0).toString()) { - this.value = PDFView.page; + this.value = PDFViewerApplication.page; } }); document.getElementById('scaleSelect').addEventListener('change', function() { - PDFView.setScale(this.value); + PDFViewerApplication.setScale(this.value, false); }); document.getElementById('presentationMode').addEventListener('click', @@ -1995,8 +1684,8 @@ function webViewerInitialized() { SecondaryToolbar.downloadClick.bind(SecondaryToolbar)); //#if (FIREFOX || MOZCENTRAL) -//PDFView.setTitleUsingUrl(file); -//PDFView.initPassiveLoading(); +//PDFViewerApplication.setTitleUsingUrl(file); +//PDFViewerApplication.initPassiveLoading(); //return; //#endif @@ -2005,18 +1694,18 @@ function webViewerInitialized() { // file:-scheme. Load the contents in the main thread because QtWebKit // cannot load file:-URLs in a Web Worker. file:-URLs are usually loaded // very quickly, so there is no need to set up progress event listeners. - PDFView.setTitleUsingUrl(file); + PDFViewerApplication.setTitleUsingUrl(file); var xhr = new XMLHttpRequest(); xhr.onload = function() { - PDFView.open(new Uint8Array(xhr.response), 0); + PDFViewerApplication.open(new Uint8Array(xhr.response), 0); }; try { xhr.open('GET', file); xhr.responseType = 'arraybuffer'; xhr.send(); } catch (e) { - PDFView.error(mozL10n.get('loading_error', null, - 'An error occurred while loading the PDF.'), e); + PDFViewerApplication.error(mozL10n.get('loading_error', null, + 'An error occurred while loading the PDF.'), e); } return; } @@ -2024,7 +1713,7 @@ function webViewerInitialized() { //#if !B2G && !CHROME if (file) { - PDFView.open(file, 0); + PDFViewerApplication.open(file, 0); } //#endif //#if CHROME @@ -2036,106 +1725,102 @@ function webViewerInitialized() { document.addEventListener('DOMContentLoaded', webViewerLoad, true); +document.addEventListener('pagerendered', function (e) { + var pageIndex = e.detail.pageNumber - 1; + var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex); + var thumbnailView = PDFViewerApplication.pdfThumbnailViewer. + getThumbnail(pageIndex); + thumbnailView.setImage(pageView.canvas); + +//#if (FIREFOX || MOZCENTRAL) +//if (pageView.textLayer && pageView.textLayer.textDivs && +// pageView.textLayer.textDivs.length > 0 && +// !PDFViewerApplication.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.')); +// PDFViewerApplication.fallback(); +//} +//#endif + + if (pageView.error) { + PDFViewerApplication.error(mozL10n.get('rendering_error', null, + 'An error occurred while rendering the page.'), pageView.error); + } + +//#if (FIREFOX || MOZCENTRAL) +//FirefoxCom.request('reportTelemetry', JSON.stringify({ +// type: 'pageInfo' +//})); +//// It is a good time to report stream and font types +//PDFViewerApplication.pdfDocument.getStats().then(function (stats) { +// FirefoxCom.request('reportTelemetry', JSON.stringify({ +// type: 'documentStats', +// stats: stats +// })); +//}); +//#endif +}, true); + +window.addEventListener('presentationmodechanged', function (e) { + var active = e.detail.active; + var switchInProgress = e.detail.switchInProgress; + PDFViewerApplication.pdfViewer.presentationModeState = + switchInProgress ? PresentationModeState.CHANGING : + active ? PresentationModeState.FULLSCREEN : PresentationModeState.NORMAL; +}); + function updateViewarea() { - - if (!PDFView.initialized) { + if (!PDFViewerApplication.initialized) { return; } - var visible = PDFView.getVisiblePages(); - var visiblePages = visible.views; - if (visiblePages.length === 0) { + PDFViewerApplication.pdfViewer.update(); +} + +window.addEventListener('updateviewarea', function () { + if (!PDFViewerApplication.initialized) { return; } - var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE, - 2 * visiblePages.length + 1); - cache.resize(suggestedCacheSize); + var location = PDFViewerApplication.pdfViewer.location; - PDFView.renderHighestPriority(visible); - - var currentId = PDFView.page; - 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 === PDFView.page) { - stillFullyVisible = true; - break; - } - } - - if (!stillFullyVisible) { - currentId = visiblePages[0].id; - } - - if (!PresentationMode.active) { - updateViewarea.inProgress = true; // used in "set page" - PDFView.page = currentId; - updateViewarea.inProgress = false; - } - - var currentScale = PDFView.currentScale; - var currentScaleValue = PDFView.currentScaleValue; - var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? - Math.round(currentScale * 10000) / 100 : currentScaleValue; - - var pageNumber = firstPage.id; - var pdfOpenParams = '#page=' + pageNumber; - pdfOpenParams += '&zoom=' + normalizedScaleValue; - var currentPage = PDFView.pages[pageNumber - 1]; - var container = PDFView.container; - var topLeft = currentPage.getPagePoint((container.scrollLeft - firstPage.x), - (container.scrollTop - firstPage.y)); - var intLeft = Math.round(topLeft[0]); - var intTop = Math.round(topLeft[1]); - pdfOpenParams += ',' + intLeft + ',' + intTop; - - if (PresentationMode.active || PresentationMode.switchInProgress) { - PDFView.currentPosition = null; - } else { - PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop }; - } - - PDFView.store.initializedPromise.then(function() { - PDFView.store.setMultiple({ + PDFViewerApplication.store.initializedPromise.then(function() { + PDFViewerApplication.store.setMultiple({ 'exists': true, - 'page': pageNumber, - 'zoom': normalizedScaleValue, - 'scrollLeft': intLeft, - 'scrollTop': intTop + 'page': location.pageNumber, + 'zoom': location.scale, + 'scrollLeft': location.left, + 'scrollTop': location.top }).catch(function() { // unable to write to storage }); }); - var href = PDFView.getAnchorUrl(pdfOpenParams); + var href = PDFViewerApplication.getAnchorUrl(location.pdfOpenParams); document.getElementById('viewBookmark').href = href; document.getElementById('secondaryViewBookmark').href = href; // Update the current bookmark in the browsing history. - PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber); -} + PDFHistory.updateCurrentBookmark(location.pdfOpenParams, location.pageNumber); +}, true); window.addEventListener('resize', function webViewerResize(evt) { - if (PDFView.initialized && + if (PDFViewerApplication.initialized && (document.getElementById('pageWidthOption').selected || document.getElementById('pageFitOption').selected || document.getElementById('pageAutoOption').selected)) { - PDFView.setScale(document.getElementById('scaleSelect').value); + var selectedScale = document.getElementById('scaleSelect').value; + PDFViewerApplication.setScale(selectedScale, false); } updateViewarea(); // Set the 'max-height' CSS property of the secondary toolbar. - SecondaryToolbar.setMaxHeight(PDFView.container); + SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer')); }); window.addEventListener('hashchange', function webViewerHashchange(evt) { if (PDFHistory.isHashChangeUnlocked) { - PDFView.setHash(document.location.hash.substring(1)); + PDFViewerApplication.setHash(document.location.hash.substring(1)); } }); @@ -2149,19 +1834,19 @@ window.addEventListener('change', function webViewerChange(evt) { if (!PDFJS.disableCreateObjectURL && typeof URL !== 'undefined' && URL.createObjectURL) { - PDFView.open(URL.createObjectURL(file), 0); + PDFViewerApplication.open(URL.createObjectURL(file), 0); } else { // Read the local file into a Uint8Array. var fileReader = new FileReader(); fileReader.onload = function webViewerChangeFileReaderOnload(evt) { var buffer = evt.target.result; var uint8Array = new Uint8Array(buffer); - PDFView.open(uint8Array, 0); + PDFViewerApplication.open(uint8Array, 0); }; fileReader.readAsArrayBuffer(file); } - PDFView.setTitleUsingUrl(file.name); + PDFViewerApplication.setTitleUsingUrl(file.name); // URL does not reflect proper document location - hiding some icons. document.getElementById('viewBookmark').setAttribute('hidden', 'true'); @@ -2190,7 +1875,7 @@ function selectScaleOption(value) { window.addEventListener('localized', function localized(evt) { document.getElementsByTagName('html')[0].dir = mozL10n.getDirection(); - PDFView.animationStartedPromise.then(function() { + PDFViewerApplication.animationStartedPromise.then(function() { // Adjust the width of the zoom box to fit the content. // Note: If the window is narrow enough that the zoom box is not visible, // we temporarily show it to be able to adjust its width. @@ -2209,7 +1894,7 @@ window.addEventListener('localized', function localized(evt) { } // Set the 'max-height' CSS property of the secondary toolbar. - SecondaryToolbar.setMaxHeight(PDFView.container); + SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer')); }); }, true); @@ -2220,7 +1905,7 @@ window.addEventListener('scalechange', function scalechange(evt) { var customScaleOption = document.getElementById('customScaleOption'); customScaleOption.selected = false; - if (!evt.resetAutoSettings && + if (!PDFViewerApplication.updateScaleControls && (document.getElementById('pageWidthOption').selected || document.getElementById('pageFitOption').selected || document.getElementById('pageAutoOption').selected)) { @@ -2228,6 +1913,12 @@ window.addEventListener('scalechange', function scalechange(evt) { return; } + if (evt.presetValue) { + selectScaleOption(evt.presetValue); + updateViewarea(); + return; + } + var predefinedValueFound = selectScaleOption('' + evt.scale); if (!predefinedValueFound) { customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%'; @@ -2238,34 +1929,27 @@ window.addEventListener('scalechange', function scalechange(evt) { window.addEventListener('pagechange', function pagechange(evt) { var page = evt.pageNumber; - if (PDFView.previousPageNumber !== page) { + if (evt.previousPageNumber !== page) { document.getElementById('pageNumber').value = 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 = PDFView.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 }); - } - } + PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page); } - var numPages = PDFView.pages.length; + var numPages = PDFViewerApplication.pagesCount; document.getElementById('previous').disabled = (page <= 1); document.getElementById('next').disabled = (page >= numPages); document.getElementById('firstPage').disabled = (page <= 1); document.getElementById('lastPage').disabled = (page >= numPages); + + // checking if the this.page was called from the updateViewarea function + if (evt.updateInProgress) { + return; + } + // Avoid scrolling the first page during loading + if (this.loading && page === 1) { + return; + } + PDFViewerApplication.getPageView(page - 1).scrollIntoView(); }, true); function handleMouseWheel(evt) { @@ -2276,9 +1960,9 @@ function handleMouseWheel(evt) { if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer evt.preventDefault(); - PDFView[direction](Math.abs(ticks)); + PDFViewerApplication[direction](Math.abs(ticks)); } else if (PresentationMode.active) { - PDFView.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR); + PDFViewerApplication.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR); } } @@ -2287,7 +1971,8 @@ window.addEventListener('mousewheel', handleMouseWheel); window.addEventListener('click', function click(evt) { if (!PresentationMode.active) { - if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) { + if (SecondaryToolbar.opened && + PDFViewerApplication.pdfViewer.containsElement(evt.target)) { SecondaryToolbar.close(); } } else if (evt.button === 0) { @@ -2314,14 +1999,15 @@ window.addEventListener('keydown', function keydown(evt) { // either CTRL or META key with optional SHIFT. switch (evt.keyCode) { case 70: // f - if (!PDFView.supportsIntegratedFind) { - PDFView.findBar.open(); + if (!PDFViewerApplication.supportsIntegratedFind) { + PDFViewerApplication.findBar.open(); handled = true; } break; case 71: // g - if (!PDFView.supportsIntegratedFind) { - PDFView.findBar.dispatchEvent('again', cmd === 5 || cmd === 12); + if (!PDFViewerApplication.supportsIntegratedFind) { + PDFViewerApplication.findBar.dispatchEvent('again', + cmd === 5 || cmd === 12); handled = true; } break; @@ -2329,13 +2015,13 @@ window.addEventListener('keydown', function keydown(evt) { case 107: // FF '+' and '=' case 187: // Chrome '+' case 171: // FF with German keyboard - PDFView.zoomIn(); + PDFViewerApplication.zoomIn(); handled = true; break; case 173: // FF/Mac '-' case 109: // FF '-' case 189: // Chrome '-' - PDFView.zoomOut(); + PDFViewerApplication.zoomOut(); handled = true; break; case 48: // '0' @@ -2343,7 +2029,7 @@ window.addEventListener('keydown', function keydown(evt) { // keeping it unhandled (to restore page zoom to 100%) setTimeout(function () { // ... and resetting the scale after browser adjusts its scale - PDFView.setScale(DEFAULT_SCALE, true); + PDFViewerApplication.setScale(DEFAULT_SCALE, true); }); handled = false; break; @@ -2355,7 +2041,7 @@ window.addEventListener('keydown', function keydown(evt) { if (cmd === 1 || cmd === 8) { switch (evt.keyCode) { case 83: // s - PDFView.download(); + PDFViewerApplication.download(); handled = true; break; } @@ -2401,20 +2087,20 @@ window.addEventListener('keydown', function keydown(evt) { case 33: // pg up case 8: // backspace if (!PresentationMode.active && - PDFView.currentScaleValue !== 'page-fit') { + PDFViewerApplication.currentScaleValue !== 'page-fit') { break; } /* in presentation mode */ /* falls through */ case 37: // left arrow // horizontal scrolling using arrow keys - if (PDFView.isHorizontalScrollbarEnabled) { + if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) { break; } /* falls through */ case 75: // 'k' case 80: // 'p' - PDFView.page--; + PDFViewerApplication.page--; handled = true; break; case 27: // esc key @@ -2422,8 +2108,9 @@ window.addEventListener('keydown', function keydown(evt) { SecondaryToolbar.close(); handled = true; } - if (!PDFView.supportsIntegratedFind && PDFView.findBar.opened) { - PDFView.findBar.close(); + if (!PDFViewerApplication.supportsIntegratedFind && + PDFViewerApplication.findBar.opened) { + PDFViewerApplication.findBar.close(); handled = true; } break; @@ -2431,32 +2118,32 @@ window.addEventListener('keydown', function keydown(evt) { case 34: // pg down case 32: // spacebar if (!PresentationMode.active && - PDFView.currentScaleValue !== 'page-fit') { + PDFViewerApplication.currentScaleValue !== 'page-fit') { break; } /* falls through */ case 39: // right arrow // horizontal scrolling using arrow keys - if (PDFView.isHorizontalScrollbarEnabled) { + if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) { break; } /* falls through */ case 74: // 'j' case 78: // 'n' - PDFView.page++; + PDFViewerApplication.page++; handled = true; break; case 36: // home - if (PresentationMode.active || PDFView.page > 1) { - PDFView.page = 1; + if (PresentationMode.active || PDFViewerApplication.page > 1) { + PDFViewerApplication.page = 1; handled = true; } break; case 35: // end - if (PresentationMode.active || (PDFView.pdfDocument && - PDFView.page < PDFView.pdfDocument.numPages)) { - PDFView.page = PDFView.pdfDocument.numPages; + if (PresentationMode.active || (PDFViewerApplication.pdfDocument && + PDFViewerApplication.page < PDFViewerApplication.pagesCount)) { + PDFViewerApplication.page = PDFViewerApplication.pagesCount; handled = true; } break; @@ -2467,7 +2154,7 @@ window.addEventListener('keydown', function keydown(evt) { } break; case 82: // 'r' - PDFView.rotatePages(90); + PDFViewerApplication.rotatePages(90); break; } } @@ -2476,15 +2163,15 @@ window.addEventListener('keydown', function keydown(evt) { switch (evt.keyCode) { case 32: // spacebar if (!PresentationMode.active && - PDFView.currentScaleValue !== 'page-fit') { + PDFViewerApplication.currentScaleValue !== 'page-fit') { break; } - PDFView.page--; + PDFViewerApplication.page--; handled = true; break; case 82: // 'r' - PDFView.rotatePages(-90); + PDFViewerApplication.rotatePages(-90); break; } } @@ -2493,21 +2180,21 @@ window.addEventListener('keydown', function keydown(evt) { // 33=Page Up 34=Page Down 35=End 36=Home // 37=Left 38=Up 39=Right 40=Down if (evt.keyCode >= 33 && evt.keyCode <= 40 && - !PDFView.container.contains(curElement)) { + !PDFViewerApplication.pdfViewer.containsElement(curElement)) { // The page container is not focused, but a page navigation key has been // pressed. Change the focus to the viewer container to make sure that // navigation by keyboard works as expected. - PDFView.container.focus(); + PDFViewerApplication.pdfViewer.focus(); } // 32=Spacebar if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') { //#if (FIREFOX || MOZCENTRAL) // // Workaround for issue in Firefox, that prevents scroll keys from // // working when elements with 'tabindex' are focused. (#3498) -// PDFView.container.blur(); +// PDFViewerApplication.pdfViewer.blur(); //#else - if (!PDFView.container.contains(curElement)) { - PDFView.container.focus(); + if (!PDFViewerApplication.pdfViewer.containsElement(curElement)) { + PDFViewerApplication.pdfViewer.focus(); } //#endif } @@ -2532,22 +2219,23 @@ window.addEventListener('keydown', function keydown(evt) { if (handled) { evt.preventDefault(); - PDFView.clearMouseScrollState(); + PDFViewerApplication.clearMouseScrollState(); } }); window.addEventListener('beforeprint', function beforePrint(evt) { - PDFView.beforePrint(); + PDFViewerApplication.beforePrint(); }); window.addEventListener('afterprint', function afterPrint(evt) { - PDFView.afterPrint(); + PDFViewerApplication.afterPrint(); }); (function animationStartedClosure() { // The offsetParent is not set until the pdf.js iframe or object is visible. // Waiting for first animation. - PDFView.animationStartedPromise = new Promise(function (resolve) { + PDFViewerApplication.animationStartedPromise = new Promise( + function (resolve) { window.requestAnimationFrame(resolve); }); })(); @@ -2559,7 +2247,7 @@ window.addEventListener('afterprint', function afterPrint(evt) { // var fileURL = activity.source.data.url; // // var url = URL.createObjectURL(blob); -// PDFView.open({url : url, originalUrl: fileURL}); +// PDFViewerApplication.open({url : url, originalUrl: fileURL}); // // var header = document.getElementById('header'); // header.addEventListener('action', function() {