How to display whole PDF (not only one page) with PDF.JS?

78,680

Solution 1

PDFJS has a member variable numPages, so you'd just iterate through them. BUT it's important to remember that getting a page in pdf.js is asynchronous, so the order wouldn't be guaranteed. So you'd need to chain them. You could do something along these lines:

var currPage = 1; //Pages are 1-based not 0-based
var numPages = 0;
var thePDF = null;

//This is where you start
PDFJS.getDocument(url).then(function(pdf) {

        //Set PDFJS global object (so we can easily access in our page functions
        thePDF = pdf;

        //How many pages it has
        numPages = pdf.numPages;

        //Start with first page
        pdf.getPage( 1 ).then( handlePages );
});



function handlePages(page)
{
    //This gives us the page's dimensions at full scale
    var viewport = page.getViewport( 1 );

    //We'll create a canvas for each page to draw it on
    var canvas = document.createElement( "canvas" );
    canvas.style.display = "block";
    var context = canvas.getContext('2d');
    canvas.height = viewport.height;
    canvas.width = viewport.width;

    //Draw it on the canvas
    page.render({canvasContext: context, viewport: viewport});

    //Add it to the web page
    document.body.appendChild( canvas );

    //Move to next page
    currPage++;
    if ( thePDF !== null && currPage <= numPages )
    {
        thePDF.getPage( currPage ).then( handlePages );
    }
}

Solution 2

Here's my take. Renders all pages in correct order and still works asynchronously.

<style>
  #pdf-viewer {
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.1);
    overflow: auto;
  }
  
  .pdf-page-canvas {
    display: block;
    margin: 5px auto;
    border: 1px solid rgba(0, 0, 0, 0.2);
  }
</style>

<script>   
    url = 'https://github.com/mozilla/pdf.js/blob/master/test/pdfs/tracemonkey.pdf';
    var thePdf = null;
    var scale = 1;
    
    PDFJS.getDocument(url).promise.then(function(pdf) {
        thePdf = pdf;
        viewer = document.getElementById('pdf-viewer');
        
        for(page = 1; page <= pdf.numPages; page++) {
          canvas = document.createElement("canvas");    
          canvas.className = 'pdf-page-canvas';         
          viewer.appendChild(canvas);            
          renderPage(page, canvas);
        }
    });
    
    function renderPage(pageNumber, canvas) {
        thePdf.getPage(pageNumber).then(function(page) {
          viewport = page.getViewport({ scale: scale });
          canvas.height = viewport.height;
          canvas.width = viewport.width;          
          page.render({canvasContext: canvas.getContext('2d'), viewport: viewport});
    });
    }
</script>

<div id='pdf-viewer'></div>

Solution 3

The pdfjs-dist library contains parts for building PDF viewer. You can use PDFPageView to render all pages. Based on https://github.com/mozilla/pdf.js/blob/master/examples/components/pageviewer.html :

var url = "https://cdn.mozilla.net/pdfjs/tracemonkey.pdf";
var container = document.getElementById('container');
// Load document
PDFJS.getDocument(url).then(function (doc) {
  var promise = Promise.resolve();
  for (var i = 0; i < doc.numPages; i++) {
    // One-by-one load pages
    promise = promise.then(function (id) {
      return doc.getPage(id + 1).then(function (pdfPage) {
// Add div with page view.
var SCALE = 1.0; 
var pdfPageView = new PDFJS.PDFPageView({
      container: container,
      id: id,
      scale: SCALE,
      defaultViewport: pdfPage.getViewport(SCALE),
      // We can enable text/annotations layers, if needed
      textLayerFactory: new PDFJS.DefaultTextLayerFactory(),
      annotationLayerFactory: new PDFJS.DefaultAnnotationLayerFactory()
    });
    // Associates the actual page with the view, and drawing it
    pdfPageView.setPdfPage(pdfPage);
    return pdfPageView.draw();        
      });
    }.bind(null, i));
  }
  return promise;
});
#container > *:not(:first-child) {
  border-top: solid 1px black; 
}
<link href="https://npmcdn.com/pdfjs-dist/web/pdf_viewer.css" rel="stylesheet"/>
<script src="https://npmcdn.com/pdfjs-dist/web/compatibility.js"></script>
<script src="https://npmcdn.com/pdfjs-dist/build/pdf.js"></script>
<script src="https://npmcdn.com/pdfjs-dist/web/pdf_viewer.js"></script>

<div id="container" class="pdfViewer singlePageView"></div>

Solution 4

The accepted answer is not working anymore (in 2021), due to the API change for var viewport = page.getViewport( 1 ); to var viewport = page.getViewport({scale: scale});, you can try the full working html as below, just copy the content below to a html file, and open it:

<html>
<head>
<script src="https://mozilla.github.io/pdf.js/build/pdf.js"></script>
<head>
<body>
</body>

<script>

var url = 'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf';

// Loaded via <script> tag, create shortcut to access PDF.js exports.
var pdfjsLib = window['pdfjs-dist/build/pdf'];

// The workerSrc property shall be specified.
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://mozilla.github.io/pdf.js/build/pdf.worker.js';

var currPage = 1; //Pages are 1-based not 0-based
var numPages = 0;
var thePDF = null;

//This is where you start
pdfjsLib.getDocument(url).promise.then(function(pdf) {

        //Set PDFJS global object (so we can easily access in our page functions
        thePDF = pdf;

        //How many pages it has
        numPages = pdf.numPages;

        //Start with first page
        pdf.getPage( 1 ).then( handlePages );
});


function handlePages(page)
{
    //This gives us the page's dimensions at full scale
    var viewport = page.getViewport( {scale: 1.5} );

    //We'll create a canvas for each page to draw it on
    var canvas = document.createElement( "canvas" );
    canvas.style.display = "block";
    var context = canvas.getContext('2d');

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

    //Draw it on the canvas
    page.render({canvasContext: context, viewport: viewport});

    //Add it to the web page
    document.body.appendChild( canvas );

    var line = document.createElement("hr");
    document.body.appendChild( line );

    //Move to next page
    currPage++;
    if ( thePDF !== null && currPage <= numPages )
    {
        thePDF.getPage( currPage ).then( handlePages );
    }
}
</script>

</html>

Solution 5

The following answer is a partial answer targeting anyone trying to get a PDF.js to display a whole PDF in 2019, as the api has changed significantly. This was of course the OP's primary concern. inspiration sample code

Please take note of the following:

  • extra libs are being used -- Lodash (for range() function) and polyfills (for promises)....
  • Bootstrap is being used
    <div class="row">
        <div class="col-md-10 col-md-offset-1">
            <div id="wrapper">

            </div>
        </div>
    </div>

    <style>
        body {
            background-color: #808080;
            /* margin: 0; padding: 0; */
        }
    </style>    
    <link href="//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.1.266/pdf_viewer.css" rel="stylesheet"/>    

    <script src="//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.1.266/pdf.js"></script>    
    <script src="//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.1.266/pdf_viewer.js"></script>
    <script src="//cdn.polyfill.io/v2/polyfill.min.js"></script>    
    <script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
    <script>
        $(document).ready(function () {
            // startup
        });

        'use strict';

        if (!pdfjsLib.getDocument || !pdfjsViewer.PDFViewer) {
            alert("Please build the pdfjs-dist library using\n" +
                "  `gulp dist-install`");
        }

        var url = '//www.pdf995.com/samples/pdf.pdf';

        pdfjsLib.GlobalWorkerOptions.workerSrc =
            '//cdnjs.cloudflare.com/ajax/libs/pdf.js/2.1.266/pdf.worker.js';

        var loadingTask = pdfjsLib.getDocument(url);
        loadingTask.promise.then(function(pdf) {
            // please be aware this uses .range() function from lodash
            var pagePromises = _.range(1, pdf.numPages).map(function(number) {
                return pdf.getPage(number);
            });
            return Promise.all(pagePromises);
        }).then(function(pages) {
                var scale = 1.5;
                var canvases = pages.forEach(function(page) {
                    var viewport = page.getViewport({ scale: scale, }); // Prepare canvas using PDF page dimensions

                    var canvas = document.createElement('canvas');
                    canvas.height = viewport.height;
                    canvas.width = viewport.width; // Render PDF page into canvas context

                    var canvasContext = canvas.getContext('2d');
                    var renderContext = {
                        canvasContext: canvasContext,
                        viewport: viewport
                    };
                    page.render(renderContext).promise.then(function() {
                        if (false)
                            return console.log('Page rendered');
                    });
                    document.getElementById('wrapper').appendChild(canvas);
                });
            },
            function(error) {
                return console.log('Error', error);
            });
    </script>
Share:
78,680
Tom Smykowski
Author by

Tom Smykowski

Updated on July 08, 2022

Comments

  • Tom Smykowski
    Tom Smykowski almost 2 years

    I've created this demo:

    http://polishwords.com.pl/dev/pdfjs/test.html

    It displays one page. I would like to display all pages. One below another, or place some buttons to change page or even better load all standard controls of PDF.JS like in Firefox. How to acomplish this?

  • Sara
    Sara over 9 years
    this doesn't work for me. My canvas is inside of a div and when run above code it shows the pdf pages on top of each other at the end of the page (not div)
  • Don Rhummy
    Don Rhummy over 9 years
    @Sara you need to learn DOM. the above code is an example only. it appends the created pages to the document. you need to put them in your div and style the canvases as needed in your project. but all that is outside the scope of this question
  • Sara
    Sara over 9 years
    Thanks for quick response:) I added div and it adds the canvas to the right place but it overwrite them..
  • Don Rhummy
    Don Rhummy over 9 years
    @Sara without seeing your code it's impossible to know what's wrong. why don't you post your code in a new question describing the issue?
  • Sara
    Sara over 9 years
  • Dr.jacky
    Dr.jacky almost 9 years
    @DonRhummy But its image! I want exactly something like mozilla.github.io/pdf.js/web/viewer.html ;;;;;;;;;;;;; text can selected and it's searchable. How can i achieve this? Thanks.
  • Don Rhummy
    Don Rhummy almost 9 years
    @Mr.Hyde i haven't looked at this project in years. it's very likely the api has methods to help with that but you can still use canvas and listen to mouse events to implement text selection.
  • JAVA_RMI
    JAVA_RMI about 7 years
    you are appending canvas to body document.body.appendChild( canvas ); but i have dynamic overlayscreen where i have to render pdf.how can i do that? i am using TOuchPDF library which is using pdf.js
  • JAVA_RMI
    JAVA_RMI about 7 years
    from where this canvasContainer coming? could you explain i have dynamic div inside which canvas will be appended after page load on clicking on repesticve links
  • JAVA_RMI
    JAVA_RMI about 7 years
    i am using TouchPDF library
  • Don Rhummy
    Don Rhummy about 7 years
    @JAVA_RMI developer.mozilla.org/en-US/docs/Web/API/Node/appendChild You can append a node to any element.
  • JAVA_RMI
    JAVA_RMI about 7 years
    @DonRhummy yeah i am trying same but can't github.com/loicminghetti/touchpdf/blob/master/…
  • Don Rhummy
    Don Rhummy about 7 years
    @JAVA_RMI I'm not going to look through all that code. That's a very long file. You need to write a test case that just gets a PDF doc and appends the page to an element to see if you can reproduce. I will point out that the loading of PDF pages is asynchronous and your "goto" code appears to assume it's synchronous.
  • Gathole
    Gathole almost 7 years
    Thank you, for giving working code snippet as I needed.
  • Mark G
    Mark G over 6 years
    Brilliant - Thank you!
  • Vael Victus
    Vael Victus over 5 years
    Simple and clean. Made it scale = 2 for web.
  • Partho63
    Partho63 over 5 years
    Give some explanation with your answer. Just giving code becomes useless.
  • coder
    coder about 5 years
    perfect solution
  • FernandoZ
    FernandoZ over 4 years
    I created my canvases first (dynamically) then I get each of them as I am iterating through the pages using document.getElementById.... you just have to give our cava
  • redfox05
    redfox05 almost 4 years
    How does this solution ensure the correct page order? Seems to me that it could still go out of order in a race condition, as you are iterating through the pages to render, but not waiting for the previous page to finish processing? Please do correct me if I misread it
  • Reto Höhener
    Reto Höhener almost 4 years
    @redfox05 the canvas elements are created and appended in order. the render function then works on the canvas that it received as argument.
  • redfox05
    redfox05 almost 4 years
    @RetoHöhener, thanks, yep I wondered that just now myself hence I came back to check if you'd replied. It just clicked when I thought about passing the references. So the canvas is created, in order, and then the reference to that is passed on to the render function, so when it finishes loading that page, it throws it into the original canvas element it came from, thus rendering it in order :) I think in my implementation I'll add some sort of ID count into the element, to make it more obvious to the next developer.
  • redfox05
    redfox05 almost 4 years
    btw, technically having the renderPage function seems obsolete as its not called from anywhere else but that loop. Im guessing that could just be left in the loop itself? Readability I guess though
  • redfox05
    redfox05 almost 4 years
    oh, slight typo. Missing a var on the viewer,page(for loop), canvas and viewport variables.
  • user11809641
    user11809641 over 3 years
    If you have to render an array of PDFs, this solution still works, but you will need to change the global variables to an array of objects with each of the properties defined above, otherwise the properties of the different PDFs will overwrite each other. See below (stackoverflow.com/a/65727954/11809641) for rendering multiple PDFs.
  • Nam Lê Quý
    Nam Lê Quý about 3 years
    "message": "Uncaught ReferenceError: PDFJS is not defined",
  • Don Rhummy
    Don Rhummy about 3 years
    Is the only change the getViewport method?
  • Sam YC
    Sam YC about 3 years
    @DonRhummy yes.
  • Clément
    Clément almost 3 years
    I changed the call to page.getViewport from page.getViewport(scale) to page.getViewport({ scale: scale }), since that's what seem to works in recent releases. github.com/mozilla/pdf.js/blob/master/src/display/api.js
  • Lil' Bits
    Lil' Bits over 2 years
    THANK YOU. You just saved me SO much work with this.
  • Tyler Houssian
    Tyler Houssian over 2 years
    This is the working answer as of 2021
  • Reejesh
    Reejesh almost 2 years
    In case anyone is looking on how to scroll to a specific page using this, keep a canvas id with the current page (like "canv1","canv2",etc) and use stackoverflow.com/a/22292000/14784590