/* Copyright 2016 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 PDFJS */ 'use strict'; if (typeof PDFJS === 'undefined' || !PDFJS.PDFViewer || !PDFJS.getDocument) { alert('Please build the pdfjs-dist library using\n' + ' `gulp dist-install`'); } var USE_ONLY_CSS_ZOOM = true; var TEXT_LAYER_MODE = 0; // DISABLE PDFJS.maxImageSize = 1024 * 1024; PDFJS.workerSrc = '../../node_modules/pdfjs-dist/build/pdf.worker.js'; PDFJS.cMapUrl = '../../node_modules/pdfjs-dist/cmaps/'; PDFJS.cMapPacked = true; var DEFAULT_URL = '../../web/compressed.tracemonkey-pldi-09.pdf'; var DEFAULT_SCALE_DELTA = 1.1; var MIN_SCALE = 0.25; var MAX_SCALE = 10.0; var DEFAULT_SCALE_VALUE = 'auto'; var PDFViewerApplication = { pdfLoadingTask: null, pdfDocument: null, pdfViewer: null, pdfHistory: null, pdfLinkService: null, /** * Opens PDF document specified by URL. * @returns {Promise} - Returns the promise, which is resolved when document * is opened. */ open: function (params) { if (this.pdfLoadingTask) { // We need to destroy already opened document return this.close().then(function () { // ... and repeat the open() call. return this.open(params); }.bind(this)); } var url = params.url; var self = this; this.setTitleUsingUrl(url); // Loading document. var loadingTask = PDFJS.getDocument(url); this.pdfLoadingTask = loadingTask; loadingTask.onProgress = function (progressData) { self.progress(progressData.loaded / progressData.total); }; return loadingTask.promise.then(function (pdfDocument) { // Document loaded, specifying document for the viewer. self.pdfDocument = pdfDocument; self.pdfViewer.setDocument(pdfDocument); self.pdfLinkService.setDocument(pdfDocument); self.pdfHistory.initialize(pdfDocument.fingerprint); self.loadingBar.hide(); self.setTitleUsingMetadata(pdfDocument); }, function (exception) { var message = exception && exception.message; var l10n = self.l10n; var loadingErrorMessage; if (exception instanceof PDFJS.InvalidPDFException) { // change error message also for other builds loadingErrorMessage = l10n.get('invalid_file_error', null, 'Invalid or corrupted PDF file.'); } else if (exception instanceof PDFJS.MissingPDFException) { // special message for missing PDFs loadingErrorMessage = l10n.get('missing_file_error', null, 'Missing PDF file.'); } else if (exception instanceof PDFJS.UnexpectedResponseException) { loadingErrorMessage = l10n.get('unexpected_response_error', null, 'Unexpected server response.'); } else { loadingErrorMessage = l10n.get('loading_error', null, 'An error occurred while loading the PDF.'); } loadingErrorMessage.then(function (msg) { self.error(msg, {message: message}); }); self.loadingBar.hide(); }); }, /** * Closes opened PDF document. * @returns {Promise} - Returns the promise, which is resolved when all * destruction is completed. */ close: function () { var errorWrapper = document.getElementById('errorWrapper'); errorWrapper.setAttribute('hidden', 'true'); if (!this.pdfLoadingTask) { return Promise.resolve(); } var promise = this.pdfLoadingTask.destroy(); this.pdfLoadingTask = null; if (this.pdfDocument) { this.pdfDocument = null; this.pdfViewer.setDocument(null); this.pdfLinkService.setDocument(null, null); } return promise; }, get loadingBar() { var bar = new PDFJS.ProgressBar('#loadingBar', {}); return PDFJS.shadow(this, 'loadingBar', bar); }, setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { this.url = url; var title = PDFJS.getFilenameFromUrl(url) || url; try { title = decodeURIComponent(title); } catch (e) { // decodeURIComponent may throw URIError, // fall back to using the unprocessed url in that case } this.setTitle(title); }, setTitleUsingMetadata: function (pdfDocument) { var self = this; pdfDocument.getMetadata().then(function(data) { var info = data.info, metadata = data.metadata; self.documentInfo = info; self.metadata = metadata; // Provides some basic debug information console.log('PDF ' + pdfDocument.fingerprint + ' [' + info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() + ' / ' + (info.Creator || '-').trim() + ']' + ' (PDF.js: ' + (PDFJS.version || '-') + (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')'); var pdfTitle; if (metadata && metadata.has('dc:title')) { var title = metadata.get('dc:title'); // Ghostscript sometimes returns 'Untitled', so prevent setting the // title to 'Untitled. if (title !== 'Untitled') { pdfTitle = title; } } if (!pdfTitle && info && info['Title']) { pdfTitle = info['Title']; } if (pdfTitle) { self.setTitle(pdfTitle + ' - ' + document.title); } }); }, setTitle: function pdfViewSetTitle(title) { document.title = title; document.getElementById('title').textContent = title; }, error: function pdfViewError(message, moreInfo) { var l10n = this.l10n; var moreInfoText = [l10n.get('error_version_info', {version: PDFJS.version || '?', build: PDFJS.build || '?'}, 'PDF.js v{{version}} (build: {{build}})')]; if (moreInfo) { moreInfoText.push( l10n.get('error_message', {message: moreInfo.message}, 'Message: {{message}}')); if (moreInfo.stack) { moreInfoText.push( l10n.get('error_stack', {stack: moreInfo.stack}, 'Stack: {{stack}}')); } else { if (moreInfo.filename) { moreInfoText.push( l10n.get('error_file', {file: moreInfo.filename}, 'File: {{file}}')); } if (moreInfo.lineNumber) { moreInfoText.push( l10n.get('error_line', {line: moreInfo.lineNumber}, 'Line: {{line}}')); } } } var errorWrapper = document.getElementById('errorWrapper'); errorWrapper.removeAttribute('hidden'); var errorMessage = document.getElementById('errorMessage'); errorMessage.textContent = message; var closeButton = document.getElementById('errorClose'); closeButton.onclick = function() { errorWrapper.setAttribute('hidden', 'true'); }; var errorMoreInfo = document.getElementById('errorMoreInfo'); var moreInfoButton = document.getElementById('errorShowMore'); var lessInfoButton = document.getElementById('errorShowLess'); moreInfoButton.onclick = function() { errorMoreInfo.removeAttribute('hidden'); moreInfoButton.setAttribute('hidden', 'true'); lessInfoButton.removeAttribute('hidden'); errorMoreInfo.style.height = errorMoreInfo.scrollHeight + 'px'; }; lessInfoButton.onclick = function() { errorMoreInfo.setAttribute('hidden', 'true'); moreInfoButton.removeAttribute('hidden'); lessInfoButton.setAttribute('hidden', 'true'); }; moreInfoButton.removeAttribute('hidden'); lessInfoButton.setAttribute('hidden', 'true'); Promise.all(moreInfoText).then(function (parts) { errorMoreInfo.value = parts.join('\n'); }); }, progress: function pdfViewProgress(level) { var percent = Math.round(level * 100); // Updating the bar if value increases. if (percent > this.loadingBar.percent || isNaN(percent)) { this.loadingBar.percent = percent; } }, get pagesCount() { return this.pdfDocument.numPages; }, set page(val) { this.pdfViewer.currentPageNumber = val; }, get page() { return this.pdfViewer.currentPageNumber; }, zoomIn: function pdfViewZoomIn(ticks) { var newScale = this.pdfViewer.currentScale; do { newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2); newScale = Math.ceil(newScale * 10) / 10; newScale = Math.min(MAX_SCALE, newScale); } while (--ticks && newScale < MAX_SCALE); this.pdfViewer.currentScaleValue = newScale; }, zoomOut: function pdfViewZoomOut(ticks) { var newScale = this.pdfViewer.currentScale; do { newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2); newScale = Math.floor(newScale * 10) / 10; newScale = Math.max(MIN_SCALE, newScale); } while (--ticks && newScale > MIN_SCALE); this.pdfViewer.currentScaleValue = newScale; }, initUI: function pdfViewInitUI() { var linkService = new PDFJS.PDFLinkService(); this.pdfLinkService = linkService; this.l10n = PDFJS.NullL10n; var container = document.getElementById('viewerContainer'); var pdfViewer = new PDFJS.PDFViewer({ container: container, linkService: linkService, l10n: this.l10n, useOnlyCssZoom: USE_ONLY_CSS_ZOOM, textLayerMode: TEXT_LAYER_MODE, }); this.pdfViewer = pdfViewer; linkService.setViewer(pdfViewer); this.pdfHistory = new PDFJS.PDFHistory({ linkService: linkService }); linkService.setHistory(this.pdfHistory); document.getElementById('previous').addEventListener('click', function() { PDFViewerApplication.page--; }); document.getElementById('next').addEventListener('click', function() { PDFViewerApplication.page++; }); document.getElementById('zoomIn').addEventListener('click', function() { PDFViewerApplication.zoomIn(); }); document.getElementById('zoomOut').addEventListener('click', function() { PDFViewerApplication.zoomOut(); }); document.getElementById('pageNumber').addEventListener('click', function() { this.select(); }); document.getElementById('pageNumber').addEventListener('change', function() { PDFViewerApplication.page = (this.value | 0); // Ensure that the page number input displays the correct value, even if the // value entered by the user was invalid (e.g. a floating point number). if (this.value !== PDFViewerApplication.page.toString()) { this.value = PDFViewerApplication.page; } }); container.addEventListener('pagesinit', function () { // We can use pdfViewer now, e.g. let's change default scale. pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE; }); container.addEventListener('pagechange', function (evt) { var page = evt.pageNumber; var numPages = PDFViewerApplication.pagesCount; document.getElementById('pageNumber').value = page; document.getElementById('previous').disabled = (page <= 1); document.getElementById('next').disabled = (page >= numPages); }, true); } }; document.addEventListener('DOMContentLoaded', function () { PDFViewerApplication.initUI(); }, true); (function animationStartedClosure() { // The offsetParent is not set until the PDF.js iframe or object is visible. // Waiting for first animation. PDFViewerApplication.animationStartedPromise = new Promise( function (resolve) { window.requestAnimationFrame(resolve); }); })(); // We need to delay opening until all HTML is loaded. PDFViewerApplication.animationStartedPromise.then(function () { PDFViewerApplication.open({ url: DEFAULT_URL }); });