32

I am using PDF.JS to render pdf pages into different canvas elements. my requirement is to capture the output of the canvas and to display it as an image. Is there some event to know if the rendering of the pdf page in canvas has been finished or not. because when I try to capture the output of canvas it is blank. but the pdf page is rendered properly. it looks like my capture event is being called before the pdf.js finishes the rendering process.

here is my code:

page.render(renderContext);
var myImage = new Image();
myImage.src = document.getElementById('my-canvas-id').toDataURL();
$('body').append(myImage);

If I execute the same code in my FireFox's console this works fine. so nothing is wrong with this code.

Just to let you people know that I already have tried document.ready and window.load events.

ted
  • 13,596
  • 9
  • 65
  • 107
Malik Ahmed Khan Awan
  • 1,920
  • 2
  • 15
  • 15

16 Answers16

22

I was also struggling with this problem.. the solution that i used is:

//Step 1: store a refer to the renderer
var pageRendering = page.render(renderContext);
//Step : hook into the pdf render complete event
var completeCallback = pageRendering.internalRenderTask.callback;
pageRendering.internalRenderTask.callback = function (error) {
  //Step 2: what you want to do before calling the complete method                  
  completeCallback.call(this, error);
  //Step 3: do some more stuff
};
Lyon
  • 711
  • 8
  • 11
  • 7
    For newer versions of PDF.js, internalRenderTask is not accessible directly. Instead, use _ internalRenderTask. So the example would be pageRendering._internalRenderTask.callback – stan Jul 23 '15 at 14:33
  • @Lyon: I would like to show a popup window once my pdf has rendered. Would I add the above code in viewer.js and call my function ? – zock Nov 25 '15 at 03:31
  • Hi Zock just saw your message. I'm not sure whats in viewer.js, but If i wanted to show a message after the pdf is rendered, then i would add it in step 3. You can also adapt either Young or Chris use of the promise object and perform the necessary operation in the function passed to the "then" method – Lyon Apr 02 '16 at 17:19
  • I do not like this answer (not putting a negative vote) because it is a page specific callback. To get a page the PDFViewerApplication element has to be already initialized what is asynchronous and not bound to document loaded event. Basically when HTML page is loaded PDFViewerApplication is not yet available. And even it was, there are multiple pages and for certain scenarios It needs them all being loaded not the one in a viewport. Leaving this comment not to downvote the response, that definitely appreciated but to highlight it might not be Ok for all the cases. – Stan Sokolov Sep 03 '21 at 18:55
  • @Lyon: I tried to apply your solution and running `PDFViewerApplication.pdfViewer.scrollPageIntoView({pageNumber: desiredPage, destArray: dest})` at step 3. So after the rendering is finished, the view would jump to a specific point in my pdf. Unfortunately, nothing happens. Do you have an idea why? – Mazze Oct 28 '22 at 19:43
18

<script type="text/javascript">
  document.addEventListener("pagesloaded", function(e) {
   //do sth..
  });
</script>

worked for me

myuce
  • 1,321
  • 1
  • 19
  • 29
  • 3
    Only solution here, where you can run `window.print();` and pdf.js is not warning, that not all pages have been rendered. Thanks! – elsamuko Dec 29 '15 at 08:41
16

At the time of writing, this did work. I'm not sure if it still does.

PDFJS makes use of Promises. The easiest thing to do is the following:

page.render(renderContext).promise.then(function(){
  document.body.appendChild(canvas);
});
sluijs
  • 4,146
  • 4
  • 30
  • 36
8

Using the textlayerrendered event worked for me:

document.addEventListener('textlayerrendered', function (event) {
  // was this the last page?
  if (event.detail.pageNumber === PDFViewerApplication.page) {
    console.log('Finished rendering!');
  }
}, true);
Luc
  • 3,581
  • 3
  • 22
  • 24
  • Where did you add this to `viewer.js`? I'm not receiving this callback. – Sakiboy Apr 02 '17 at 06:00
  • Note how `event.target` usefully returns the `
    ` for the given text layer that has just been added to the DOM. @Sakiboy It would be best to chain it after the `PDFViewerApplication.animationStartedPromise` Promise, ideally before calling `PDFViewerApplication.open()`.
    – Jamie Birch Sep 18 '18 at 15:02
4

Lyon's solution of more robust. But this is the simplest solution I could find.

var renderTask = pdfPage.render(renderContext);
renderTask.promise.then(
  function pdfPageRenderCallback() {
    pageViewDrawCallback(null);
  },
  function pdfPageRenderError(error) {
    pageViewDrawCallback(error);
  }
);
4
window.addEventListener('load', function () {
    PDFViewerApplication.eventBus.on('textlayerrendered', function () {
      console.log('textlayerrendered', arguments, PDFViewerApplication)
    })

Event name textlayerrendered includes of PDFViewerApplication.eventBus._listeners

  • This was working about half the time for me, but had some race conditions where it could fail. I've found listening for the `PDFViewerApplication.initializedPromise` promise to be more reliable - see my answer. – nnyby Jul 22 '21 at 17:22
3

While examining these solutions, I was having issues getting the renderContext, so I ended up using this approach listening to pagerendered:

document.addEventListener("pagerendered", function(e){

});

In my case, I just wanted to call some external function after the page was rendered, with minimal effort.

Hope this helps. Cheers!

Rich
  • 3,928
  • 4
  • 37
  • 66
barbossusus
  • 539
  • 4
  • 4
3

According to documentation from PDF.js.

for versions before 2.2 use:
page.render(renderContext).then(function(){
var myImage = new Image();
myImage.src = document.getElementById('my-canvas-id').toDataURL();
$('body').append(myImage);
});

For versions after and including 2.2 use: page.render(renderContext).promise.then(function(){
var myImage = new Image();
myImage.src = document.getElementById('my-canvas-id').toDataURL();
$('body').append(myImage);
});

Dan
  • 41
  • 1
3

If you're using pdf.js with the viewer application, here's the best way I've found to wait for it to be initialized:

// Wait for the PDFViewerApplication to initialize                                                                                                                         
PDFViewerApplication.initializedPromise.then(function() {
    // Do whatever startup code you need to here, now that the EventBus
    // is read. For example:
    PDFViewerApplication.eventBus.on('pagerendered', function (e) {
        annotationInterface.onPageRendered(e);
    });
});
nnyby
  • 4,748
  • 10
  • 49
  • 105
2

As it turns out, your first line, page.render(renderContext); returns a RenderTask object which has 3 properties:

  • internalRenderTask - an InternalRenderTask, duh
  • cancel - a function, presumably to allow you to cancel the rendering effort
  • promise - a jQuery Promise object

The Promise is the one you want. To make use of it, it goes like this:

 page.render(renderContext).promise.then(function() {

     //do something after the page is rendered here
 });

Hope that helps.

MichaelMao
  • 2,596
  • 2
  • 23
  • 54
witttness
  • 4,984
  • 4
  • 25
  • 24
  • This is the right answer, but it appears not to work quite right https://github.com/mozilla/pdf.js/issues/12858 – Kevin Beal Jan 13 '21 at 16:19
2

Depending on which components (and version) of PDF.js you are making use of, you can also use the EventBus.

If you are using the simplest implementation of PDF.js then this might not be available, but if you are using their SimpleViewer or PageViewer examples, try this:

        eventBus.on("pagesloaded", function() {
            console.log('pagesloaded');
            // your code here
        });

eventBus should already be defined, if not, you can set it up using:

var eventBus = new pdfjsViewer.EventBus();

Again, this depends on the level of complexity in your PDF Viewer. Turns out there are many different layers within PDF.js.

To listen for every page load, use the eventBus event 'pagerendered'

redfox05
  • 3,354
  • 1
  • 34
  • 39
1

I have changed my code in this way and it helped me what I wanted to do:

pageRendering = page.render(renderContext);
pageRendering.onData(function(){
    var myImage = new Image();
    myImage.src = document.getElementById('my-canvas-id').toDataURL();
    $('body').append(myImage);
});

This helps only if the specific page has finished rendering. it doesn't tell you about the rendering of all of the pages.

Malik Ahmed Khan Awan
  • 1,920
  • 2
  • 15
  • 15
  • as far as i know. pdf js just render page that you see and next page. in other word, if your position is on page 1 when you first load. pdf.js just render page 1 and page 2. the other page will rendered when you enter "page-1" – Sunu Pinasthika Fajar Oct 12 '12 at 02:18
  • @Malik : Did you add this code to viewer.js ? I'm trying to open a popup window once the pdf has finished rendering so wanted to understand how and where to implement this. Thanks! – zock Nov 25 '15 at 03:19
1

If you want to render all pages of pdf document in different canvases, all one by one synchronously this is kind of solution:

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>PDF Sample</title>
    <script type="text/javascript" src="jquery.js"></script>
    <script type="text/javascript" src="pdf.js"></script>
    <script type="text/javascript" src="main.js">
    </script>
    <link rel="stylesheet" type="text/css" href="main.css">
</head>
<body id="body">  
</body>
</html>

main.css

canvas {
    display: block;
}

main.js

$(function() {  
    var filePath = "document.pdf";

    function Num(num) {
        var num = num;

        return function () {
            return num;
        }
    };

    function renderPDF(url, canvasContainer, options) {
        var options = options || {
                scale: 1.5
            },          
            func,
            pdfDoc,
            def = $.Deferred(),
            promise = $.Deferred().resolve().promise(),         
            width, 
            height,
            makeRunner = function(func, args) {
                return function() {
                    return func.call(null, args);
                };
            };

        function renderPage(num) {          
            var def = $.Deferred(),
                currPageNum = new Num(num);
            pdfDoc.getPage(currPageNum()).then(function(page) {
                var viewport = page.getViewport(options.scale);
                var canvas = document.createElement('canvas');
                var ctx = canvas.getContext('2d');
                var renderContext = {
                    canvasContext: ctx,
                    viewport: viewport
                };

                if(currPageNum() === 1) {                   
                    height = viewport.height;
                    width = viewport.width;
                }

                canvas.height = height;
                canvas.width = width;

                canvasContainer.appendChild(canvas);

                page.render(renderContext).then(function() {                                        
                    def.resolve();
                });
            })

            return def.promise();
        }

        function renderPages(data) {
            pdfDoc = data;

            var pagesCount = pdfDoc.numPages;
            for (var i = 1; i <= pagesCount; i++) { 
                func = renderPage;
                promise = promise.then(makeRunner(func, i));
            }
        }

        PDFJS.disableWorker = true;
        PDFJS.getDocument(url).then(renderPages);       
    };

    var body = document.getElementById("body");
    renderPDF(filePath, body);
});
Vladimir Trifonov
  • 1,395
  • 11
  • 9
0

page.render is a promise so you have to do your stuff inside your success like below:

page.render({
  canvasContext: context,
  viewport: viewport
}).then(function() {
//do your stuff here });
KhAn SaAb
  • 5,248
  • 5
  • 31
  • 52
Kiran
  • 43
  • 1
  • 3
0

This is the only way I've found to use the Viewer API and manipulate rendered pages without errors.

onPagesLoaded = () => { 
    // do something, set zoom, current page, ...
};

window.PDFViewerApplication.eventBus.on('pagesloaded', onPagesLoaded);
Paul
  • 1,410
  • 1
  • 14
  • 30
0

If you are creating/modifying the PDF on the backend you can add pdfjavascript to the PDF file to print on load.

var pp = getPrintParams();  
pp.interactive=pp.constants.interactionLevel.full; 
print(pp);