Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring of printing code and mozPrintCallback polyfill #7697

Merged
merged 3 commits into from
Oct 11, 2016

Conversation

yurydelendik
Copy link
Contributor

@yurydelendik yurydelendik commented Oct 7, 2016

Two things happens: moves all print related code to the single file and makes the polyfill as a first-class citizen. My finding says that Firefox now rasterizes mozPrintCallback output which aligns it with other browsers by functionality. Another finding (and lots of bugs confirm) that mozPrintCallback will not print using font that were not used during viewing. So migrating generic/demo viewer to not use mozPrintCallback will be good first step to produce consistent results across browsers. Converting canvas to PNG saves memory and improves print quality for Chrome.

return pdfPage.render(renderContext).promise;
}).then(function() {
img.src = canvas.toDataURL();
wrapper.appendChild(img);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To try and save memory you probably want to clear the canvas explicitly as soon as it's no longer needed, as is done e.g. in

pdf.js/web/pdf_page_view.js

Lines 170 to 174 in f8bd3d4

// Zeroing the width and height causes Firefox to release graphics
// resources immediately, which can greatly reduce memory consumption.
this.canvas.width = 0;
this.canvas.height = 0;
delete this.canvas;
.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: using one canvas for all pages and disposing it at the end.

<progress value="0" max="100"></progress>
<span class="relative-progress">0%</span>
</div>
<div class="row">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you want class="buttonRow" instead here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@yurydelendik
Copy link
Contributor Author

yurydelendik commented Oct 7, 2016

I didn't decide about one thing:

  • remove "pageStyleSheet with A4 selector"-support (I'm not sure how it works)

@yurydelendik
Copy link
Contributor Author

After reading the comment about bug 851441, I removed page hack for firefox print service in the second commit.

}
// 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.
var alertNotReady = !this.pdfViewer.pageViewsReady;
if (alertNotReady) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point I don't think that you need the intermediate alertNotReady variable any more, so
if (!this.pdfViewer.pageViewsReady) { should suffice here.

if (this.printing) {
this.printService.destroy();
this.printService = null;
this.printing = false;
Copy link
Collaborator

@Snuffleupagus Snuffleupagus Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the way that this.printing/this.printService is now used, we (basically) track the same state twice.
I'm wondering if we couldn't just get rid of the this.printing from this file and just check the new this.printService instead (possibly with !!this.printService where we actually want a boolean, such as e.g. in forceRendering)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed by adding "printing" getter (just in case if people use the property) or as in case above (for simplicity). Internally in app.js it's mostly used as printService


function renderPage(pdfDocument, pageNumber, size, wrapper) {
if (!scratchCanvas) {
scratchCanvas = document.createElement('canvas');
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: indentation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Rob--W
Copy link
Member

Rob--W commented Oct 7, 2016

Why did you remove the mozPrintCallback polyfill? Now the code has two code paths, one where mozPrintCallback is available and one where it is not. How does this not affect printing behavior (or how does the change improve the behavior)?

@yurydelendik
Copy link
Contributor Author

yurydelendik commented Oct 7, 2016

Why did you remove the mozPrintCallback polyfill?

code was just moved to the pdf_print_service.js

Now the code has two code paths, one where mozPrintCallback is available and one where it is not.

For simplicity, web/demo viewer will never use mozPrintCallback -- it is in bad shape now (see 1308259 and 1308579) and performs rasterization like we do, and yes, for the extension we continue using mozPrintCallback just not create a drastic change. If decision to improve/remove mozPrintCallback will be made we can either use firefox_print_callback in the pdf_print_callback or remove firefox_print_callback.

How does this not affect printing behavior (or how does the change improve the behavior)?

The changes are:

  • Firefox in the demo viewer prints the same way IE or Chrome and quality of output shall be a little bit better (due to png)
  • No style hacks in the Firefox extension code
  • Better readability of the printing logic within viewer
  • Ability to replace CANVAS/PNG with SVG output

@Rob--W, I hope I answered the questions. Thanks

Copy link
Contributor

@timvandermeij timvandermeij left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick pass through the code and these are my findings so far. In general I really like the direction of this patch, but this still requires more in-depth review and testing.

var CSS_UNITS = uiUtils.CSS_UNITS;
var PDFPrintServiceFactory = app.PDFPrintServiceFactory;

function composePage(pdfDocument, pageNumber, size, printContainer) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is called renderPage in pdf_print_service.js. Can we name it renderPage here too for consistency?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, I added a comments to pdf_print_service

dispatchEvent('beforeprint');
} finally {
if (!activeService) {
console.error('Expected print service to the initialized.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: to be initialized

throw new Error('The print service is created and active.');
}
activeService = new PDFPrintService(pdfDocument, pagesOverview,
printContainer);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: add one more space

* Returns sizes of the pages.
* @returns {Array} Array of objects with width/height fields.
*/
getPagesOverview: function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would getPageSizes perhaps be a better name, or do you want to keep it general to add more properties later on?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latter, but I don't want to think now what to add :)

@timvandermeij
Copy link
Contributor

I think that this supersedes #7408 and fixes #3783 as well, judging by the code changes, but I did not check that yet.

Copy link
Member

@Rob--W Rob--W left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good. I have found a few minor issues, commented below.

Now the mozPrintCallback polyfill is integrated, it does not need to be so generic any more and the implementation can be simplified. But I can do that since I exactly know the purpose of each line in the polyfill.

return pdfPage.render(renderContext).promise;
}).then(function() {
img.src = scratchCanvas.toDataURL();
wrapper.appendChild(img);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that you have to wait until img.onload is fired.

return new Promise(function(resolve, reject) {
  img.onload = resolve;
  img.onerror = reject;
});

Some food for thought: Would canvas.toBlob() over toDataURL improve memory usage or performance? In theory, after painting the browser does not need to keep the very long data URL around, which leads to a reduced memory usage. Here is a code sample: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Getting_a_file_representing_the_canvas

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed and improved.

scratchCanvas = null;
}
ensureOverlay().then(function () {
OverlayManager.close('printServiceOverlay');
Copy link
Member

@Rob--W Rob--W Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I send a print request and close the print dialog, an error is thrown here:

Uncaught (in promise) Error: The overlay is currently not active.(…) overlay_manager.js:117
(anonymous function) @ overlay_manager.js:117
overlayManagerClose @ overlay_manager.js:113
(anonymous function) @ pdf_print_service.js:140

I think that the fact that destroy() is called twice causes this issue.

  1. abort below calls .destroy()
  2. In app.js, the afterprint event handler calls destroy() as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check to not perform double close() added

@@ -162,6 +162,13 @@ var PDFViewer = (function pdfViewer() {
},

/**
* @returns {boolean} true if all page views are initialized.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is a "page view"? The data structure behind a PDF page? A canvas element?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment fixed.

<div id="printServiceOverlay" class="container hidden">
<div class="dialog">
<div class="row">
<span>Preparing document for printing...</span>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The localization comment disappeared. Don't we want to localize this any more?

Copy link
Contributor

@timvandermeij timvandermeij Oct 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good find. I really think we should localize this and I think it's easier now that it's part of viewer.html. Otherwise we should add back the comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment does not work with proprocessor

intent: 'print'
};
return pdfPage.render(renderContext).promise;
}).then(function() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need an if-check here to see if the print job is still active.


renderPages: function () {
var pageCount = this.pagesOverview.length;
var renderNextPage = function (resolve, reject) {
Copy link
Member

@Rob--W Rob--W Oct 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need an if-check to see if the print request is still active, and exit if it is not.

var progressPerc = progressContainer.querySelector('.relative-progress');
progressBar.value = progress;
progressPerc.textContent = progress + '%';
progressContainer.onclick = abort;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned on IRC, I think that the onclick handler should be attached to the printCancel button rather than the entire overlay container.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@yurydelendik
Copy link
Contributor Author

Nits addressed and additional checks are added.

/botio-linux preview

Copy link
Collaborator

@Snuffleupagus Snuffleupagus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

r=me; but let's have Rob and Tim sign off on this too before merging.
In my testing this works great, and the re-factoring improves readability of the code quite a bit!

@Rob--W
Copy link
Member

Rob--W commented Oct 10, 2016

Looks good from the users perspective.

  • Tested in Firefox 49 on Linux
  • Tested in Firefox 49 on macOS
  • Tested in Chromium 53 on Linux
  • Tested as a Chrome extension in Chrome 54 on Linux
  • Test in Safari 10 on macOS
  • Test as a Firefox addon in Firefox
  • Test in IE (version?)
  • Test in Edge on Windows

The unticked checkboxes are still a TODO. Any volunteers? ;)

Tested scenarios:

  • Ctrl-P, wait, close platform's print dialog.
  • Ctrl-P, Esc
  • Ctrl-P, Ctrl-P, Esc
  • Click Print button
  • Ctrl-P/Print button, click Cancel button in PDF.js dialog.

Note: When I use Esc while the PDF is being prepared, in Firefox the following error appears after a few seconds. I think that the dialog is already closed by the esc keydown handler in the OverlayManager, when the .destroy() call's overlay promise resolves and closes the dialog.

Error: The overlay is currently not active.viewer.js:333:15
(put breakpoint + printed new Error().stack to get the following stack trace)
overlayManagerClose/<@http://107.21.233.14:8877/be6471bdd1701a6/web/viewer.js:333:9
overlayManagerClose@http://107.21.233.14:8877/be6471bdd1701a6/web/viewer.js:329:12
PDFPrintService.prototype.destroy/<@http://107.21.233.14:8877/be6471bdd1701a6/web/viewer.js:9543:9

@timvandermeij
Copy link
Contributor

timvandermeij commented Oct 10, 2016

Tomorrow I'll have access to a computer with Windows, so I can take care of the remaining checkboxes and review this as well so we can land it.

@yurydelendik Could you look into the error mentioned above? The OverlayManager does have a key binding for the Esc key: https://github.com/mozilla/pdf.js/blob/master/web/overlay_manager.js#L135

@yurydelendik
Copy link
Contributor Author

Could you look into the error mentioned above?

Added guard against closing closed overlay and removed esc handling from print service.

}, this);
if (!hasEqualPageSizes) {
console.warn('Not all pages have the same size. The printed ' +
'result may be incorrect!');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove one space

if (scratchCanvas) {
scratchCanvas.width = scratchCanvas.height = 0;
scratchCanvas = null;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we clear this.wrappers and the associated divs here as well to save memory?

@timvandermeij
Copy link
Contributor

/botio-linux preview

@pdfjsbot
Copy link

From: Bot.io (Linux)


Received

Command cmd_preview from @timvandermeij received. Current queue size: 0

Live output at: http://107.21.233.14:8877/e5a7d5049c83aeb/output.txt

@pdfjsbot
Copy link

From: Bot.io (Linux)


Success

Full output at http://107.21.233.14:8877/e5a7d5049c83aeb/output.txt

Total script time: 1.02 mins

Published

@timvandermeij timvandermeij merged commit fb5aa58 into mozilla:master Oct 11, 2016
@timvandermeij
Copy link
Contributor

Awesome work! I tested this in all major browsers and found no issues.

@webmobiletechie
Copy link

I am facing similar issues while trying to print huge PDFs (that are being rendered using PDFJS) on IE11. Upgrading to latest version of PDFJS hasn't helped either. The above conversation hints that the issue may be already fixed. Where can I get the sample code? Any pointers?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants