diff --git a/gulpfile.js b/gulpfile.js index b1386534381c3..cd874d35b57d4 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -246,11 +246,11 @@ function createWebBundle(defines) { template = 'web/viewer.js'; files = ['app.js']; if (defines.FIREFOX || defines.MOZCENTRAL) { - files.push('firefoxcom.js'); + files.push('firefoxcom.js', 'firefox_print_service.js'); } else if (defines.CHROME) { - files.push('chromecom.js', 'mozPrintCallback_polyfill.js'); + files.push('chromecom.js', 'pdf_print_service.js'); } else if (defines.GENERIC) { - files.push('mozPrintCallback_polyfill.js'); + files.push('pdf_print_service.js'); } } diff --git a/l10n/en-US/viewer.properties b/l10n/en-US/viewer.properties index b545d0b9c69cb..f75ceabbb9e31 100644 --- a/l10n/en-US/viewer.properties +++ b/l10n/en-US/viewer.properties @@ -88,6 +88,12 @@ document_properties_version=PDF Version: document_properties_page_count=Page Count: document_properties_close=Close +print_progress_message=Preparing document for printing… +# LOCALIZATION NOTE (print_progress_percent): "{{progress}}" will be replaced by +# a numerical per cent value. +print_progress_percent={{progress}}% +print_progress_close=Cancel + # Tooltips and alt text for side panel toolbar buttons # (the _label strings are alt text for the buttons, the .title strings are # tooltips) diff --git a/web/app.js b/web/app.js index e9f64c23153da..016a0764dfc37 100644 --- a/web/app.js +++ b/web/app.js @@ -143,7 +143,7 @@ var PDFViewerApplication = { appConfig: null, pdfDocument: null, pdfLoadingTask: null, - printing: false, + printService: null, /** @type {PDFViewer} */ pdfViewer: null, /** @type {PDFThumbnailViewer} */ @@ -428,11 +428,12 @@ var PDFViewerApplication = { return this.pdfViewer.currentPageNumber; }, - get supportsPrinting() { - var canvas = document.createElement('canvas'); - var value = 'mozPrintCallback' in canvas; + get printing() { + return !!this.printService; + }, - return pdfjsLib.shadow(this, 'supportsPrinting', value); + get supportsPrinting() { + return PDFPrintServiceFactory.instance.supportsPrinting; }, get supportsFullscreen() { @@ -1099,6 +1100,13 @@ var PDFViewerApplication = { }, beforePrint: function pdfViewSetupBeforePrint() { + if (this.printService) { + // There is no way to suppress beforePrint/afterPrint events, + // but PDFPrintService may generate double events -- this will ignore + // the second event that will be coming from native window.print(). + return; + } + if (!this.supportsPrinting) { var printMessage = mozL10n.get('printing_not_supported', null, 'Warning: Printing is not fully supported by this browser.'); @@ -1106,59 +1114,23 @@ var PDFViewerApplication = { return; } - var alertNotReady = false; - var i, ii; - if (!this.pdfDocument || !this.pagesCount) { - alertNotReady = true; - } else { - for (i = 0, ii = this.pagesCount; i < ii; ++i) { - if (!this.pdfViewer.getPageView(i).pdfPage) { - alertNotReady = true; - break; - } - } - } - if (alertNotReady) { + // The beforePrint is a sync method and we need to know layout before + // returning from this method. Ensure that we can get sizes of the pages. + if (!this.pdfViewer.pageViewsReady) { var notReadyMessage = mozL10n.get('printing_not_ready', null, 'Warning: The PDF is not fully loaded for printing.'); window.alert(notReadyMessage); return; } - this.printing = true; + var pagesOverview = this.pdfViewer.getPagesOverview(); + var printContainer = this.appConfig.printContainer; + var printService = PDFPrintServiceFactory.instance.createPrintService( + this.pdfDocument, pagesOverview, printContainer); + this.printService = printService; this.forceRendering(); - var printContainer = this.appConfig.printContainer; - var body = document.querySelector('body'); - body.setAttribute('data-mozPrintCallback', true); - - if (!this.hasEqualPageSizes) { - console.warn('Not all pages have the same size. The printed result ' + - 'may be incorrect!'); - } - - // Insert a @page + size rule to make sure that the page size is correctly - // set. Note that we assume that all pages have the same size, because - // variable-size pages are not supported yet (at least in Chrome & Firefox). - // TODO(robwu): Use named pages when size calculation bugs get resolved - // (e.g. https://crbug.com/355116) AND when support for named pages is - // added (http://www.w3.org/TR/css3-page/#using-named-pages). - // In browsers where @page + size is not supported (such as Firefox, - // https://bugzil.la/851441), the next stylesheet will be ignored and the - // user has to select the correct paper size in the UI if wanted. - this.pageStyleSheet = document.createElement('style'); - var pageSize = this.pdfViewer.getPageView(0).pdfPage.getViewport(1); - this.pageStyleSheet.textContent = - // "size: " is what we need. But also add "A4" because - // Firefox incorrectly reports support for the other value. - '@supports ((size:A4) and (size:1pt 1pt)) {' + - '@page { size: ' + pageSize.width + 'pt ' + pageSize.height + 'pt;}' + - '}'; - body.appendChild(this.pageStyleSheet); - - for (i = 0, ii = this.pagesCount; i < ii; ++i) { - this.pdfViewer.getPageView(i).beforePrint(printContainer); - } + printService.layout(); //#if !PRODUCTION if (true) { @@ -1186,17 +1158,10 @@ var PDFViewerApplication = { }, afterPrint: function pdfViewSetupAfterPrint() { - var div = this.appConfig.printContainer; - while (div.hasChildNodes()) { - div.removeChild(div.lastChild); - } - - if (this.pageStyleSheet && this.pageStyleSheet.parentNode) { - this.pageStyleSheet.parentNode.removeChild(this.pageStyleSheet); - this.pageStyleSheet = null; + if (this.printService) { + this.printService.destroy(); + this.printService = null; } - - this.printing = false; this.forceRendering(); }, @@ -2330,6 +2295,17 @@ window.addEventListener('afterprint', function afterPrint(evt) { }); })(); +/* Abstract factory for the print service. */ +var PDFPrintServiceFactory = { + instance: { + supportsPrinting: false, + createPrintService: function () { + throw new Error('Not implemented: createPrintService'); + } + } +}; + exports.PDFViewerApplication = PDFViewerApplication; exports.DefaultExernalServices = DefaultExernalServices; +exports.PDFPrintServiceFactory = PDFPrintServiceFactory; })); diff --git a/web/firefox_print_service.js b/web/firefox_print_service.js new file mode 100644 index 0000000000000..a461ef09c0941 --- /dev/null +++ b/web/firefox_print_service.js @@ -0,0 +1,122 @@ +/* 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. + */ + +'use strict'; + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('pdfjs-web/firefox_print_service', ['exports', 'pdfjs-web/ui_utils', + 'pdfjs-web/app', 'pdfjs-web/pdfjs'], factory); + } else if (typeof exports !== 'undefined') { + factory(exports, require('./ui_utils.js'), require('./app.js'), + require('./pdfjs.js')); + } else { + factory((root.pdfjsWebFirefoxPrintService = {}), root.pdfjsWebUIUtils, + root.pdfjsWebApp, root.pdfjsWebPDFJS); + } +}(this, function (exports, uiUtils, app, pdfjsLib) { + var CSS_UNITS = uiUtils.CSS_UNITS; + var PDFPrintServiceFactory = app.PDFPrintServiceFactory; + + // Creates a placeholder with div and canvas with right size for the page. + function composePage(pdfDocument, pageNumber, size, printContainer) { + var canvas = document.createElement('canvas'); + + // The size of the canvas in pixels for printing. + var PRINT_RESOLUTION = 150; + var PRINT_UNITS = PRINT_RESOLUTION / 72.0; + canvas.width = Math.floor(size.width * PRINT_UNITS); + canvas.height = Math.floor(size.height * PRINT_UNITS); + + // The physical size of the canvas as specified by the PDF document. + canvas.style.width = Math.floor(size.width * CSS_UNITS) + 'px'; + canvas.style.height = Math.floor(size.height * CSS_UNITS) + 'px'; + + var canvasWrapper = document.createElement('div'); + canvasWrapper.appendChild(canvas); + printContainer.appendChild(canvasWrapper); + + canvas.mozPrintCallback = function(obj) { + // Printing/rendering the page. + var ctx = obj.context; + + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.restore(); + + pdfDocument.getPage(pageNumber).then(function (pdfPage) { + var renderContext = { + canvasContext: ctx, + transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], + viewport: pdfPage.getViewport(1), + intent: 'print' + }; + return pdfPage.render(renderContext).promise; + }).then(function() { + // Tell the printEngine that rendering this canvas/page has finished. + obj.done(); + }, function(error) { + console.error(error); + // Tell the printEngine that rendering this canvas/page has failed. + // This will make the print process stop. + if ('abort' in obj) { + obj.abort(); + } else { + obj.done(); + } + }); + }; + } + + function FirefoxPrintService(pdfDocument, pagesOverview, printContainer) { + this.pdfDocument = pdfDocument; + this.pagesOverview = pagesOverview; + this.printContainer = printContainer; + } + + FirefoxPrintService.prototype = { + layout: function () { + var pdfDocument = this.pdfDocument; + var printContainer = this.printContainer; + var body = document.querySelector('body'); + body.setAttribute('data-pdfjsprinting', true); + + for (var i = 0, ii = this.pagesOverview.length; i < ii; ++i) { + composePage(pdfDocument, i + 1, this.pagesOverview[i], printContainer); + } + }, + + destroy: function () { + this.printContainer.textContent = ''; + } + }; + + PDFPrintServiceFactory.instance = { + get supportsPrinting() { + var canvas = document.createElement('canvas'); + var value = 'mozPrintCallback' in canvas; + + return pdfjsLib.shadow(this, 'supportsPrinting', value); + }, + + createPrintService: function (pdfDocument, pagesOverview, printContainer) { + return new FirefoxPrintService(pdfDocument, pagesOverview, + printContainer); + } + }; + + exports.FirefoxPrintService = FirefoxPrintService; +})); diff --git a/web/mozPrintCallback_polyfill.js b/web/mozPrintCallback_polyfill.js deleted file mode 100644 index e8fde3c7120c9..0000000000000 --- a/web/mozPrintCallback_polyfill.js +++ /dev/null @@ -1,162 +0,0 @@ -/* Copyright 2013 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 HTMLCanvasElement */ - -'use strict'; - -(function (root, factory) { - if (typeof define === 'function' && define.amd) { - define('pdfjs-web/mozPrintCallback_polyfill', ['exports'], factory); - } else if (typeof exports !== 'undefined') { - factory(exports); - } else { - factory((root.pdfjsWebMozPrintCallbackPolyfill = {})); - } -}(this, function (exports) { -//#if !(FIREFOX || MOZCENTRAL) - if ('mozPrintCallback' in document.createElement('canvas')) { - return; - } - - // Cause positive result on feature-detection: - HTMLCanvasElement.prototype.mozPrintCallback = undefined; - - var canvases; // During print task: non-live NodeList of elements - var index; // Index of element that is being processed - - var print = window.print; - window.print = function print() { - if (canvases) { - console.warn('Ignored window.print() because of a pending print job.'); - return; - } - try { - dispatchEvent('beforeprint'); - } finally { - canvases = document.querySelectorAll('canvas'); - index = -1; - next(); - } - }; - - function dispatchEvent(eventType) { - var event = document.createEvent('CustomEvent'); - event.initCustomEvent(eventType, false, false, 'custom'); - window.dispatchEvent(event); - } - - function next() { - if (!canvases) { - return; // Print task cancelled by user (state reset in abort()) - } - - renderProgress(); - if (++index < canvases.length) { - var canvas = canvases[index]; - if (typeof canvas.mozPrintCallback === 'function') { - canvas.mozPrintCallback({ - context: canvas.getContext('2d'), - abort: abort, - done: next - }); - } else { - next(); - } - } else { - renderProgress(); - // Push window.print in the macrotask queue to avoid being affected by - // the deprecation of running print() code in a microtask, see - // https://github.com/mozilla/pdf.js/issues/7547. - setTimeout(function() { - if (!canvases) { - return; // Print task cancelled by user. - } - print.call(window); - setTimeout(abort, 20); // Tidy-up - }, 0); - } - } - - function abort() { - if (canvases) { - canvases = null; - renderProgress(); - dispatchEvent('afterprint'); - } - } - - function renderProgress() { - var progressContainer = document.getElementById('mozPrintCallback-shim'); - if (canvases && canvases.length) { - var progress = Math.round(100 * index / canvases.length); - var progressBar = progressContainer.querySelector('progress'); - var progressPerc = progressContainer.querySelector('.relative-progress'); - progressBar.value = progress; - progressPerc.textContent = progress + '%'; - progressContainer.removeAttribute('hidden'); - progressContainer.onclick = abort; - } else { - progressContainer.setAttribute('hidden', ''); - } - } - - var hasAttachEvent = !!document.attachEvent; - - window.addEventListener('keydown', function(event) { - // Intercept Cmd/Ctrl + P in all browsers. - // Also intercept Cmd/Ctrl + Shift + P in Chrome and Opera - if (event.keyCode === 80/*P*/ && (event.ctrlKey || event.metaKey) && - !event.altKey && (!event.shiftKey || window.chrome || window.opera)) { - window.print(); - if (hasAttachEvent) { - // Only attachEvent can cancel Ctrl + P dialog in IE <=10 - // attachEvent is gone in IE11, so the dialog will re-appear in IE11. - return; - } - event.preventDefault(); - if (event.stopImmediatePropagation) { - event.stopImmediatePropagation(); - } else { - event.stopPropagation(); - } - return; - } - if (event.keyCode === 27 && canvases) { // Esc - abort(); - } - }, true); - if (hasAttachEvent) { - document.attachEvent('onkeydown', function(event) { - event = event || window.event; - if (event.keyCode === 80/*P*/ && event.ctrlKey) { - event.keyCode = 0; - return false; - } - }); - } - - if ('onbeforeprint' in window) { - // Do not propagate before/afterprint events when they are not triggered - // from within this polyfill. (FF/IE). - var stopPropagationIfNeeded = function(event) { - if (event.detail !== 'custom' && event.stopImmediatePropagation) { - event.stopImmediatePropagation(); - } - }; - window.addEventListener('beforeprint', stopPropagationIfNeeded, false); - window.addEventListener('afterprint', stopPropagationIfNeeded, false); - } -//#endif -})); diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 927d9b0d3fae6..ee87bf8b2b36c 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -540,59 +540,6 @@ var PDFPageView = (function PDFPageViewClosure() { } return promise; }, - - beforePrint: function PDFPageView_beforePrint(printContainer) { - var CustomStyle = pdfjsLib.CustomStyle; - var pdfPage = this.pdfPage; - - var viewport = pdfPage.getViewport(1); - - var canvas = document.createElement('canvas'); - - // The size of the canvas in pixels for printing. - var PRINT_RESOLUTION = 150; - var PRINT_UNITS = PRINT_RESOLUTION / 72.0; - canvas.width = Math.floor(viewport.width * PRINT_UNITS); - canvas.height = Math.floor(viewport.height * PRINT_UNITS); - - // The physical size of the canvas as specified by the PDF document. - canvas.style.width = Math.floor(viewport.width * CSS_UNITS) + 'px'; - canvas.style.height = Math.floor(viewport.height * CSS_UNITS) + 'px'; - - var canvasWrapper = document.createElement('div'); - canvasWrapper.appendChild(canvas); - printContainer.appendChild(canvasWrapper); - - canvas.mozPrintCallback = function(obj) { - var ctx = obj.context; - - ctx.save(); - ctx.fillStyle = 'rgb(255, 255, 255)'; - ctx.fillRect(0, 0, canvas.width, canvas.height); - ctx.restore(); - - var renderContext = { - canvasContext: ctx, - transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], - viewport: viewport, - intent: 'print' - }; - - pdfPage.render(renderContext).promise.then(function() { - // Tell the printEngine that rendering this canvas/page has finished. - obj.done(); - }, function(error) { - console.error(error); - // Tell the printEngine that rendering this canvas/page has failed. - // This will make the print process stop. - if ('abort' in obj) { - obj.abort(); - } else { - obj.done(); - } - }); - }; - }, }; return PDFPageView; diff --git a/web/pdf_print_service.js b/web/pdf_print_service.js new file mode 100644 index 0000000000000..4ddac428dc659 --- /dev/null +++ b/web/pdf_print_service.js @@ -0,0 +1,311 @@ +/* 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. + */ + +'use strict'; + +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define('pdfjs-web/pdf_print_service', ['exports', 'pdfjs-web/ui_utils', + 'pdfjs-web/overlay_manager', 'pdfjs-web/app', 'pdfjs-web/pdfjs'], + factory); + } else if (typeof exports !== 'undefined') { + factory(exports, require('./ui_utils.js'), require('./overlay_manager.js'), + require('./app.js'), require('./pdfjs.js')); + } else { + factory((root.pdfjsWebPDFPrintService = {}), root.pdfjsWebUIUtils, + root.pdfjsWebOverlayManager, root.pdfjsWebApp, root.pdfjsWebPDFJS); + } +}(this, function (exports, uiUtils, overlayManager, app, pdfjsLib) { + var mozL10n = uiUtils.mozL10n; + var CSS_UNITS = uiUtils.CSS_UNITS; + var PDFPrintServiceFactory = app.PDFPrintServiceFactory; + var OverlayManager = overlayManager.OverlayManager; + + var activeService = null; + + // Using one canvas for all paint operations -- painting one canvas at a time. + var scratchCanvas = null; + + function renderPage(pdfDocument, pageNumber, size, wrapper) { + if (!scratchCanvas) { + scratchCanvas = document.createElement('canvas'); + } + + // The size of the canvas in pixels for printing. + var PRINT_RESOLUTION = 150; + var PRINT_UNITS = PRINT_RESOLUTION / 72.0; + scratchCanvas.width = Math.floor(size.width * PRINT_UNITS); + scratchCanvas.height = Math.floor(size.height * PRINT_UNITS); + + // The physical size of the img as specified by the PDF document. + var img = document.createElement('img'); + img.style.width = Math.floor(size.width * CSS_UNITS) + 'px'; + img.style.height = Math.floor(size.height * CSS_UNITS) + 'px'; + + var ctx = scratchCanvas.getContext('2d'); + ctx.save(); + ctx.fillStyle = 'rgb(255, 255, 255)'; + ctx.fillRect(0, 0, scratchCanvas.width, scratchCanvas.height); + ctx.restore(); + + return pdfDocument.getPage(pageNumber).then(function (pdfPage) { + var renderContext = { + canvasContext: ctx, + transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0], + viewport: pdfPage.getViewport(1), + intent: 'print' + }; + return pdfPage.render(renderContext).promise; + }).then(function() { + if (!activeService) { + return Promise.reject(new Error('cancelled')); + } + if (('toBlob' in scratchCanvas) && + !pdfjsLib.PDFJS.disableCreateObjectURL) { + scratchCanvas.toBlob(function (blob) { + img.src = URL.createObjectURL(blob); + }); + } else { + img.src = scratchCanvas.toDataURL(); + } + wrapper.appendChild(img); + return new Promise(function(resolve, reject) { + img.onload = resolve; + img.onerror = reject; + }); + }); + } + + function PDFPrintService(pdfDocument, pagesOverview, printContainer) { + this.pdfDocument = pdfDocument; + this.pagesOverview = pagesOverview; + this.printContainer = printContainer; + this.wrappers = []; + this.currentPage = -1; + } + + PDFPrintService.prototype = { + layout: function () { + var pdfDocument = this.pdfDocument; + var printContainer = this.printContainer; + var body = document.querySelector('body'); + body.setAttribute('data-pdfjsprinting', true); + + var hasEqualPageSizes = this.pagesOverview.every(function (size) { + return size.width === this.pagesOverview[0].width && + size.height === this.pagesOverview[0].height; + }, this); + if (!hasEqualPageSizes) { + console.warn('Not all pages have the same size. The printed ' + + 'result may be incorrect!'); + } + + // Insert a @page + size rule to make sure that the page size is correctly + // set. Note that we assume that all pages have the same size, because + // variable-size pages are not supported yet (e.g. in Chrome & Firefox). + // TODO(robwu): Use named pages when size calculation bugs get resolved + // (e.g. https://crbug.com/355116) AND when support for named pages is + // added (http://www.w3.org/TR/css3-page/#using-named-pages). + // In browsers where @page + size is not supported (such as Firefox, + // https://bugzil.la/851441), the next stylesheet will be ignored and the + // user has to select the correct paper size in the UI if wanted. + this.pageStyleSheet = document.createElement('style'); + var pageSize = this.pagesOverview[0]; + this.pageStyleSheet.textContent = + // "size: " is what we need. But also add "A4" because + // Firefox incorrectly reports support for the other value. + '@supports ((size:A4) and (size:1pt 1pt)) {' + + '@page { size: ' + pageSize.width + 'pt ' + pageSize.height + 'pt;}' + + '}'; + body.appendChild(this.pageStyleSheet); + + for (var i = 0, ii = this.pagesOverview.length; i < ii; ++i) { + var wrapper = document.createElement('div'); + printContainer.appendChild(wrapper); + this.wrappers[i] = wrapper; + } + }, + + destroy: function () { + this.printContainer.textContent = ''; + this.wrappers = null; + if (this.pageStyleSheet && this.pageStyleSheet.parentNode) { + this.pageStyleSheet.parentNode.removeChild(this.pageStyleSheet); + this.pageStyleSheet = null; + } + if (activeService !== this) { + return; // no need to clean up shared resources + } + activeService = null; + if (scratchCanvas) { + scratchCanvas.width = scratchCanvas.height = 0; + scratchCanvas = null; + } + ensureOverlay().then(function () { + if (OverlayManager.active !== 'printServiceOverlay') { + return; // overlay was already closed + } + OverlayManager.close('printServiceOverlay'); + }); + }, + + renderPages: function () { + var pageCount = this.pagesOverview.length; + var renderNextPage = function (resolve, reject) { + if (activeService !== this) { + reject(new Error('cancelled')); + return; + } + if (++this.currentPage >= pageCount) { + renderProgress(pageCount, pageCount); + resolve(); + return; + } + var index = this.currentPage; + renderProgress(index, pageCount); + renderPage(this.pdfDocument, index + 1, + this.pagesOverview[index], this.wrappers[index]).then( + function () { renderNextPage(resolve, reject); }, reject); + }.bind(this); + return new Promise(renderNextPage); + }, + }; + + + var print = window.print; + window.print = function print() { + if (activeService) { + console.warn('Ignored window.print() because of a pending print job.'); + return; + } + ensureOverlay().then(function () { + OverlayManager.open('printServiceOverlay'); + }); + + try { + dispatchEvent('beforeprint'); + } finally { + if (!activeService) { + console.error('Expected print service to be initialized.'); + } + activeService.renderPages().then(startPrint, abort); + } + }; + + function dispatchEvent(eventType) { + var event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventType, false, false, 'custom'); + window.dispatchEvent(event); + } + + function startPrint() { + // Push window.print in the macrotask queue to avoid being affected by + // the deprecation of running print() code in a microtask, see + // https://github.com/mozilla/pdf.js/issues/7547. + setTimeout(function() { + if (!activeService) { + return; // Print task cancelled by user. + } + print.call(window); + setTimeout(abort, 20); // Tidy-up + }, 0); + } + + function abort() { + if (activeService) { + activeService.destroy(); + dispatchEvent('afterprint'); + } + } + + function renderProgress(index, total) { + var progressContainer = document.getElementById('printServiceOverlay'); + var progress = Math.round(100 * index / total); + var progressBar = progressContainer.querySelector('progress'); + var progressPerc = progressContainer.querySelector('.relative-progress'); + progressBar.value = progress; + progressPerc.textContent = mozL10n.get('print_progress_percent', + {progress: progress}, progress + '%'); + } + + var hasAttachEvent = !!document.attachEvent; + + window.addEventListener('keydown', function(event) { + // Intercept Cmd/Ctrl + P in all browsers. + // Also intercept Cmd/Ctrl + Shift + P in Chrome and Opera + if (event.keyCode === 80/*P*/ && (event.ctrlKey || event.metaKey) && + !event.altKey && (!event.shiftKey || window.chrome || window.opera)) { + window.print(); + if (hasAttachEvent) { + // Only attachEvent can cancel Ctrl + P dialog in IE <=10 + // attachEvent is gone in IE11, so the dialog will re-appear in IE11. + return; + } + event.preventDefault(); + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } else { + event.stopPropagation(); + } + return; + } + }, true); + if (hasAttachEvent) { + document.attachEvent('onkeydown', function(event) { + event = event || window.event; + if (event.keyCode === 80/*P*/ && event.ctrlKey) { + event.keyCode = 0; + return false; + } + }); + } + + if ('onbeforeprint' in window) { + // Do not propagate before/afterprint events when they are not triggered + // from within this polyfill. (FF/IE). + var stopPropagationIfNeeded = function(event) { + if (event.detail !== 'custom' && event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } + }; + window.addEventListener('beforeprint', stopPropagationIfNeeded, false); + window.addEventListener('afterprint', stopPropagationIfNeeded, false); + } + + var overlayPromise; + function ensureOverlay() { + if (!overlayPromise) { + overlayPromise = OverlayManager.register('printServiceOverlay', + document.getElementById('printServiceOverlay'), abort, true); + document.getElementById('printCancel').onclick = abort; + } + return overlayPromise; + } + + PDFPrintServiceFactory.instance = { + supportsPrinting: true, + + createPrintService: function (pdfDocument, pagesOverview, printContainer) { + if (activeService) { + throw new Error('The print service is created and active.'); + } + activeService = new PDFPrintService(pdfDocument, pagesOverview, + printContainer); + return activeService; + } + }; + + exports.PDFPrintService = PDFPrintService; +})); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index cab20525424b3..6b47781c3c1c4 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -161,6 +161,13 @@ var PDFViewer = (function pdfViewer() { return this._pages[index]; }, + /** + * @returns {boolean} true if all {PDFPageView} objects are initialized. + */ + get pageViewsReady() { + return this._pageViewsReady; + }, + /** * @returns {number} */ @@ -309,6 +316,7 @@ var PDFViewer = (function pdfViewer() { }); this.pagesPromise = pagesPromise; pagesPromise.then(function () { + self._pageViewsReady = true; self.eventBus.dispatch('pagesloaded', { source: self, pagesCount: pagesCount @@ -414,6 +422,7 @@ var PDFViewer = (function pdfViewer() { this._location = null; this._pagesRotation = 0; this._pagesRequests = []; + this._pageViewsReady = false; var container = this.viewer; while (container.hasChildNodes()) { @@ -877,6 +886,17 @@ var PDFViewer = (function pdfViewer() { setFindController: function (findController) { this.findController = findController; }, + + /** + * Returns sizes of the pages. + * @returns {Array} Array of objects with width/height fields. + */ + getPagesOverview: function () { + return this._pages.map(function (pageView) { + var viewport = pageView.pdfPage.getViewport(1); + return {width: viewport.width, height: viewport.height}; + }); + }, }; return PDFViewer; diff --git a/web/viewer-snippet-mozPrintCallback-polyfill.html b/web/viewer-snippet-mozPrintCallback-polyfill.html deleted file mode 100644 index ebf2e3419d121..0000000000000 --- a/web/viewer-snippet-mozPrintCallback-polyfill.html +++ /dev/null @@ -1,71 +0,0 @@ - diff --git a/web/viewer.css b/web/viewer.css index 66e9e137d4fd0..4fedfe591384e 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -1795,11 +1795,11 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * { display: none; } - /* Rules for browsers that support mozPrintCallback */ - body[data-mozPrintCallback] #outerContainer { + /* Rules for browsers that support PDF.js printing */ + body[data-pdfjsprinting] #outerContainer { display: none; } - body[data-mozPrintCallback] #printContainer { + body[data-pdfjsprinting] #printContainer { display: block; } #printContainer { @@ -1816,7 +1816,8 @@ html[dir='rtl'] #documentPropertiesOverlay .row > * { page-break-after: always; page-break-inside: avoid; } - #printContainer canvas { + #printContainer canvas, + #printContainer img { display: block; } } diff --git a/web/viewer.html b/web/viewer.html index 169ffa978396c..676b56cda6e08 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -360,6 +360,22 @@ + + + @@ -367,8 +383,5 @@
- - - diff --git a/web/viewer.js b/web/viewer.js index e955896c1768f..5782914c9ca30 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -175,7 +175,7 @@ function webViewerLoad() { // Ensure that src/main_loader.js has loaded all the necessary dependencies // *before* the viewer loads, to prevent issues in browsers relying on e.g. // the Promise/URL polyfill in src/shared/util.js (fixes issue 7448). - require(['pdfjs-web/app', 'mozPrintCallback_polyfill.js'], function (web) { + require(['pdfjs-web/app', 'pdfjs-web/pdf_print_service'], function (web) { window.PDFViewerApplication = web.PDFViewerApplication; web.PDFViewerApplication.run(config); });