diff --git a/extensions/b2g/viewer.css b/extensions/b2g/viewer.css index 94519bfafb3f4b..2d494e91f21715 100644 --- a/extensions/b2g/viewer.css +++ b/extensions/b2g/viewer.css @@ -14,7 +14,6 @@ */ * { - box-sizing: border-box; padding: 0; margin: 0; } @@ -148,22 +147,7 @@ canvas { display: block; } -.page { - direction: ltr; - width: 81.6rem; - height: 105.6rem; - margin: 1rem auto; - position: relative; - overflow: hidden; - background-color: white; -} - -.page > a { - display: block; - position: absolute; -} - -.loadingIcon { +.pdfViewer .page .loadingIcon { width: 2.9rem; height: 2.9rem; background: url("images/spinner.png") no-repeat left top / 38rem ; diff --git a/extensions/b2g/viewer.html b/extensions/b2g/viewer.html index 83aad6c835dcec..7baa1f7f412867 100644 --- a/extensions/b2g/viewer.html +++ b/extensions/b2g/viewer.html @@ -20,22 +20,25 @@ PDF.js viewer + + + - - - - - + + + + - - - - - + + + + + +
@@ -55,7 +58,7 @@

-
+
@@ -81,194 +84,5 @@

- - - - - diff --git a/extensions/b2g/viewer.js b/extensions/b2g/viewer.js new file mode 100644 index 00000000000000..d4c18a6d359c78 --- /dev/null +++ b/extensions/b2g/viewer.js @@ -0,0 +1,350 @@ +/* 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 PDFJS, Promise */ + +'use strict'; + +PDFJS.useOnlyCssZoom = true; +PDFJS.disableTextLayer = true; +PDFJS.maxImageSize = 1024 * 1024; +PDFJS.workerSrc = '../pdfjs-components/build/pdf.worker.js'; +PDFJS.cMapUrl = '../pdfjs-components/cmaps/'; +PDFJS.cMapPacked = true; + +var DEFAULT_SCALE_DELTA = 1.1; +var MIN_SCALE = 0.25; +var MAX_SCALE = 10.0; +var DEFAULT_SCALE_VALUE = 'auto'; + +var PDFViewerApplication = { + pdfDocument: null, + pdfViewer: null, + pdfHistory: null, + pdfLinkService: null, + loading: true, + + open: function (params) { + var url = params.url, originalUrl = params.originalUrl; + var self = this; + this.setTitleUsingUrl(originalUrl); + + // Loading document. + var loadingTask = PDFJS.getDocument(url); + loadingTask.onProgress = function (progressData) { + self.progress(progressData.loaded / progressData.total); + }; + loadingTask.then(function (pdfDocument) { + // Document loaded, specifying document for the viewer. + this.pdfDocument = pdfDocument; + this.pdfViewer.setDocument(pdfDocument); + this.pdfLinkService.setDocument(pdfDocument); + this.pdfHistory.initialize(pdfDocument.fingerprint); + + this.loadingBar.hide(); + this.setTitleUsingMetadata(pdfDocument); + }.bind(this), function (exception) { + var message = exception && exception.message; + var loadingErrorMessage = mozL10n.get('loading_error', null, + 'An error occurred while loading the PDF.'); + + if (exception instanceof PDFJS.InvalidPDFException) { + // change error message also for other builds + loadingErrorMessage = mozL10n.get('invalid_file_error', null, + 'Invalid or corrupted PDF file.'); + } else if (exception instanceof PDFJS.MissingPDFException) { + // special message for missing PDFs + loadingErrorMessage = mozL10n.get('missing_file_error', null, + 'Missing PDF file.'); + } else if (exception instanceof PDFJS.UnexpectedResponseException) { + loadingErrorMessage = mozL10n.get('unexpected_response_error', null, + 'Unexpected server response.'); + } + + var moreInfo = { + message: message + }; + self.error(loadingErrorMessage, moreInfo); + self.loadingBar.hide(); + }); + }, + + get loadingBar() { + var bar = new PDFJS.ProgressBar('#loadingBar', {}); + + return PDFJS.shadow(this, 'loadingBar', bar); + }, + + setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) { + this.url = url; + var title = PDFJS.getFileName(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('activityTitle').textContent = title; + }, + + error: function pdfViewError(message, moreInfo) { + var moreInfoText = mozL10n.get('error_version_info', + {version: PDFJS.version || '?', build: PDFJS.build || '?'}, + 'PDF.js v{{version}} (build: {{build}})') + '\n'; + + if (moreInfo) { + moreInfoText += + mozL10n.get('error_message', {message: moreInfo.message}, + 'Message: {{message}}'); + if (moreInfo.stack) { + moreInfoText += '\n' + + mozL10n.get('error_stack', {stack: moreInfo.stack}, + 'Stack: {{stack}}'); + } else { + if (moreInfo.filename) { + moreInfoText += '\n' + + mozL10n.get('error_file', {file: moreInfo.filename}, + 'File: {{file}}'); + } + if (moreInfo.lineNumber) { + moreInfoText += '\n' + + mozL10n.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'); + errorMoreInfo.value = moreInfoText; + }, + + 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; + + var container = document.getElementById('viewerContainer'); + var pdfViewer = new PDFJS.PDFViewer({ + container: container, + linkService: linkService + }); + 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() { + // Handle the user inputting a floating point number. + PDFViewerApplication.page = (this.value | 0); + + if (this.value !== (this.value | 0).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; + if (evt.previousPageNumber !== page) { + document.getElementById('pageNumber').value = page; + } + var numPages = PDFViewerApplication.pagesCount; + + 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); + }); +})(); + +// Support of the new version of navigator.mozL10n -- in PDF.js older/custom +// version is used. +var mozL10n = { + get: function (id, args, fallback) { + var s = (navigator.mozL10n && navigator.mozL10n.get(id)) || fallback; + s = s.replace(/\{\{\s*(\w+)\s*\}\}/g, function (all, key) { + return args[key] || ''; + }); + return s; + }, + + translate: function (fragment) { + if (navigator.mozL10n) { + navigator.mozL10n.translateFragment(fragment); + } + } +}; + +window.navigator.mozSetMessageHandler('activity', function(activity) { + var blob = activity.source.data.blob; + var fileURL = activity.source.data.url || + activity.source.data.filename || + ' '; // if no url or filename, use a non-empty string + + var url = URL.createObjectURL(blob); + // We need to delay opening until all HTML is loaded. + PDFViewerApplication.animationStartedPromise.then(function () { + PDFViewerApplication.open({url: url, originalUrl: fileURL}); + + var header = document.getElementById('header'); + header.addEventListener('action', function() { + activity.postResult('close'); + }); + }); +}); diff --git a/make.js b/make.js index 0c8380c38d9395..83aff5b14fd340 100644 --- a/make.js +++ b/make.js @@ -979,7 +979,8 @@ target.mozcentral = function() { }; target.b2g = function() { - target.locale(); + target.generic(); + target.components(); echo(); echo('### Building B2G (Firefox OS App)'); @@ -991,9 +992,11 @@ target.b2g = function() { cd(ROOT_DIR); rm('-Rf', B2G_BUILD_DIR); mkdir('-p', B2G_BUILD_CONTENT_DIR); - mkdir('-p', B2G_BUILD_CONTENT_DIR + BUILD_DIR); mkdir('-p', B2G_BUILD_CONTENT_DIR + '/web'); - mkdir('-p', B2G_BUILD_CONTENT_DIR + '/web/cmaps'); + // Simulating pdfjs-dist structure in the pdfjs-components folder. + mkdir('-p', B2G_BUILD_CONTENT_DIR + '/pdfjs-components/web'); + mkdir('-p', B2G_BUILD_CONTENT_DIR + '/pdfjs-components/build'); + mkdir('-p', B2G_BUILD_CONTENT_DIR + '/pdfjs-components/cmaps'); var setup = { defines: defines, @@ -1001,14 +1004,21 @@ target.b2g = function() { ['extensions/b2g/images', B2G_BUILD_CONTENT_DIR + '/web'], ['extensions/b2g/viewer.html', B2G_BUILD_CONTENT_DIR + '/web'], ['extensions/b2g/viewer.css', B2G_BUILD_CONTENT_DIR + '/web'], + ['extensions/b2g/viewer.js', B2G_BUILD_CONTENT_DIR + '/web'], ['web/locale', B2G_BUILD_CONTENT_DIR + '/web'], - ['external/bcmaps/*', B2G_BUILD_CONTENT_DIR + '/web/cmaps'], - ['external/webL10n/l10n.js', B2G_BUILD_CONTENT_DIR + '/web'] + ['build/generic/build/pdf.js', + B2G_BUILD_CONTENT_DIR + '/pdfjs-components/build'], + ['build/generic/build/pdf.worker.js', + B2G_BUILD_CONTENT_DIR + '/pdfjs-components/build'], + ['build/components/pdf_viewer.js', + B2G_BUILD_CONTENT_DIR + '/pdfjs-components/web'], + ['build/components/pdf_viewer.css', + B2G_BUILD_CONTENT_DIR + '/pdfjs-components/web'], + ['build/components/images', + B2G_BUILD_CONTENT_DIR + '/pdfjs-components/web'], + ['external/bcmaps/*', B2G_BUILD_CONTENT_DIR + '/pdfjs-components/cmaps'] ], - preprocess: [ - ['web/viewer.js', B2G_BUILD_CONTENT_DIR + '/web'], - [BUILD_TARGETS, B2G_BUILD_CONTENT_DIR + BUILD_DIR] - ] + preprocess: [] }; builder.build(setup); diff --git a/web/pdf_viewer.component.js b/web/pdf_viewer.component.js index c59e98233d89c1..cb3dbad99b4a68 100644 --- a/web/pdf_viewer.component.js +++ b/web/pdf_viewer.component.js @@ -17,7 +17,7 @@ /*jshint globalstrict: false */ /* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder, PDFLinkService, DefaultTextLayerFactory, AnnotationsLayerBuilder, PDFHistory, - DefaultAnnotationsLayerFactory, getFileName */ + DefaultAnnotationsLayerFactory, getFileName, ProgressBar */ // Initializing PDFJS global object (if still undefined) if (typeof PDFJS === 'undefined') { @@ -42,4 +42,5 @@ if (typeof PDFJS === 'undefined') { PDFJS.PDFHistory = PDFHistory; PDFJS.getFileName = getFileName; + PDFJS.ProgressBar = ProgressBar; }).call((typeof window === 'undefined') ? this : window); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 75dcf32a149305..a719d62019bf7e 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -18,7 +18,7 @@ SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS, DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates, PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue, - AnnotationsLayerBuilder */ + AnnotationsLayerBuilder, DEFAULT_SCALE_VALUE */ 'use strict'; @@ -149,7 +149,8 @@ var PDFViewer = (function pdfViewer() { * @returns {number} */ get currentScale() { - return this._currentScale; + return this._currentScale !== UNKNOWN_SCALE ? this._currentScale : + DEFAULT_SCALE; }, /** @@ -161,7 +162,7 @@ var PDFViewer = (function pdfViewer() { } if (!this.pdfDocument) { this._currentScale = val; - this._currentScaleValue = val.toString(); + this._currentScaleValue = val !== UNKNOWN_SCALE ? val.toString() : null; return; } this._setScale(val, false); @@ -265,7 +266,7 @@ var PDFViewer = (function pdfViewer() { // 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 scale = this.currentScale; var viewport = pdfPage.getViewport(scale * CSS_UNITS); for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) { var textLayerFactory = null; @@ -523,10 +524,10 @@ var PDFViewer = (function pdfViewer() { return; } - if (scale && scale !== this.currentScale) { + if (scale && scale !== this._currentScale) { this.currentScaleValue = scale; - } else if (this.currentScale === UNKNOWN_SCALE) { - this.currentScaleValue = DEFAULT_SCALE; + } else if (this._currentScale === UNKNOWN_SCALE) { + this.currentScaleValue = DEFAULT_SCALE_VALUE; } if (scale === 'page-fit' && !dest[4]) { diff --git a/web/ui_utils.js b/web/ui_utils.js index b8128f21233bb0..e58b77106ee448 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -17,7 +17,8 @@ 'use strict'; var CSS_UNITS = 96.0 / 72.0; -var DEFAULT_SCALE = 'auto'; +var DEFAULT_SCALE_VALUE = 'auto'; +var DEFAULT_SCALE = 1.0; var UNKNOWN_SCALE = 0; var MAX_AUTO_SCALE = 1.25; var SCROLLBAR_PADDING = 40; diff --git a/web/viewer.js b/web/viewer.js index ea09d868a99267..fff17f46e91447 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -22,8 +22,8 @@ Promise, PDFLinkService, PDFOutlineView, PDFAttachmentView, OverlayManager, PDFFindController, PDFFindBar, getVisibleElements, watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState, - parseQueryString, RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE, - IGNORE_CURRENT_POSITION_ON_ZOOM: true */ + parseQueryString, RenderingStates, UNKNOWN_SCALE, + DEFAULT_SCALE_VALUE, IGNORE_CURRENT_POSITION_ON_ZOOM: true */ 'use strict'; @@ -994,10 +994,10 @@ var PDFViewerApplication = { this.page = 1; } - if (this.pdfViewer.currentScale === UNKNOWN_SCALE) { + if (!this.pdfViewer.currentScaleValue) { // Scale was not initialized: invalid bookmark or scale was not specified. // Setting the default one. - this.setScale(DEFAULT_SCALE, true); + this.setScale(DEFAULT_SCALE_VALUE, true); } }, @@ -1903,7 +1903,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 - PDFViewerApplication.setScale(DEFAULT_SCALE, true); + PDFViewerApplication.setScale(DEFAULT_SCALE_VALUE, true); }); handled = false; }