PDF.JS Tutorial - How to display a PDF with Javascript

Mozilla's PDF.JS is PDF viewer made with HTML5 technologies. It can help your application in custom rendering of PDF files — showing a PDF file in a div in your HTML, browsing through the pages of the PDF through Javascript and a few more features.

Please note that PDF.JS is just a PDF viewer and not a PDF editor. It is not a library to create PDF files.

PDF.JS is used by Firefox internally to display PDF files. In fact PDF.JS is so good that even Opera decided to use it.

Demo

Click on the button below to choose a PDF file :

Loading document ...
Page
of
Loading page ...

Download Sample Codes

Download

PDF Viewer Javascript Plugin

A premium responsive PDF Viewer plugin is also available. It has some advanced features like embedding multiple PDF files in a single page, viewing PDF files when a link is clicked, modal & full-screen mode, finding out whether user has fully viewed the PDF etc.

Previewing PDF files at the Time of Upload

Giving the user to preview his PDF files before uploading makes an application look good. In some cases, the application may require the user to choose a few pages of the PDF. In such cases PDF.JS is of help.

An Overview of Javascript Promises

Almost everything in PDF.JS is done asynchronously — from loading the PDF file to loading individual pages to rendering pages.

This means that return from such asynchronous operations have to be handled through callbacks. Now PDF.JS does not use normal callbacks, it uses Promises.

Promises and callbacks do almost the same thing, but Promises helps in writing cleaner code. Instead of writing callbacks (such as success or error) here and there in your code, you just chain the various callbacks involved by a then (for success) or catch (for error).

promise_variable.then(function(some_parameter) { 
    /* success */
}).catch(function(some_parameter) {
    /* error */
});

Coming back to PDF.JS, it returns a Promise variable on each asynchronous operation request. When the asynchronous operation is completed, the Promise involved receives a parameter in the callback function. PDF.JS APIs work in this way.

PDF.JS APIs Used in this Tutorial

When you include the script files of PDF.JS, you get a PDFJS global object. This object provides a number of APIs that you can call in your Javscript code.

PDFJS.getDocument({ url: pdf_url })
It loads the PDF file. The return value is a Promise, which when resolved, passes a PDFDocumentProxy object in the callback function. In simple words, PDFDocumentProxy is the handle of the current PDF file.
pdf_url can be a url to a PDF file in your server (Cross Domain is not allowed unless CORS is enabled). In the case when the user chooses a PDF file from his computer, you can get the local url of the PDF file through URL.createObjectURL function.
You can also pass binary data as a parameter. If you have a base-64 encoded data, you can convert it to a binary string through atob function.

// Normal url or an object url
PDFJS.getDocument({ url: 'http://yourserver.com/sample.pdf' }).then(function(pdf_doc) {
    // pdf_doc is a PDFDocumentProxy object
});

// binary data
PDFJS.getDocument({ data: binary_data }).then(function(pdf_doc) {
    // pdf_doc is a PDFDocumentProxy object
});

pdf_doc.numPages
It is not a method, but a read-only property that gets the number of pages in the PDF file.

var total_pages = pdf_doc.numPages;

pdf_doc.getPage(page_no)
It loads the specified page of the PDF. The return value is a Promise, which when resolved, passes a PDFPageProxy object in the callback function. In simple words, PDFPageProxy is the handle of the chosen page in the PDF file. Note that this function just loads the page, and not renders the page on the screen.

pdf_doc.getPage(page_no).then(function(page) {
    // page is a PDFPageProxy object
});

page.getViewport(scale)
It returns the dimensions of the current page (in px) of the PDF at a specified zoom level. This is not an asynchronous call, so dimensions are returned immediately.

// Get viewport at scale = 1
var viewport = page.getViewport(1);

page.render(renderContext)
It renders the current page of the PDF on the screen. The return value is a Promise.
Rendering can be done in either a <canvas> or <svg> element. In this tutorial, a canvas element is used.
renderContext is a key-value object that holds viewport properties.

// Get viewport at scale = 1
var viewport = page.getViewport(1);

// page is rendered on a <canvas> element
var renderContext = {
    canvasContext: document.getElementById('pdf-canvas').getContext('2d'),
    viewport: viewport
};

page.render(renderContext).then(function() {
    // page has rendered
});

Writing Code, Step 1 : Including PDF.JS Script Files

Go to PDF.JS Github Page and download the files. There will be 2 files in the "build" directory. Include them in your HTML. In this tutorial, version 1.5 of PDF.JS has been used.

<script src="js/pdf.js"></script>
<script src="js/pdf.worker.js"></script>

PDF.JS files are pretty huge. It is better if you minify them. You can use an online UglifyJS minifier.

Step 2 : Preparing HTML

<button id="upload-button">Select PDF</button> 
<input type="file" id="file-to-upload" accept="application/pdf" />

<div id="pdf-main-container">
    <div id="pdf-loader">Loading document ...</div>
    <div id="pdf-contents">
        <div id="pdf-meta">
            <div id="pdf-buttons">
                <button id="pdf-prev">Previous</button>
                <button id="pdf-next">Next</button>
            </div>
            <div id="page-count-container">Page <div id="pdf-current-page"></div> of <div id="pdf-total-pages"></div></div>
        </div>
        <canvas id="pdf-canvas" width="400"></canvas>
        <div id="page-loader">Loading page ...</div>
    </div>
</div>

  • #file-to-upload is the file input type through which the user chooses a PDF.
  • #pdf-loader is the container where a "PDF Loading" message would be shown while the PDF is being loaded.
  • #pdf-prev & #pdf-next are buttons that will go the Previous & Next page of the PDF.
  • #pdf-current-page will hold the current page no of the PDF.
  • #pdf-total-pages will hold the total number pages in the PDF.
  • #pdf-canvas is the canvas element where the PDF will be rendered.
  • #page-loader will show a "Page Loading" message while a page is being rendered.

Step 3 : Defining some Javascript variables

We need a few global variables that will hold properties used throughout the code.

var __PDF_DOC,
    __CURRENT_PAGE,
    __TOTAL_PAGES,
    __PAGE_RENDERING_IN_PROGRESS = 0,
    __CANVAS = $('#pdf-canvas').get(0),
    __CANVAS_CTX = __CANVAS.getContext('2d');

  • __PDF_DOC will hold the PDFDocumentProxy object that is passed into the callback of the getDocumentPromise.
  • __CURRENT_PAGE will hold the current page number. __TOTAL_PAGES will hold the total no of pages in the PDF.
  • __PAGE_RENDERING_IN_PROGRESS is a flag that will hold whether a currently being rendered or not. If rendering is in progress Previous & Next buttons will be disabled so as not to cause a page-content mismatch. Remember page rendering is asynchronous, it will take at least a few milliseconds to render a page.
  • __CANVAS and __CANVAS_CTX will hold the canvas element and its context respectively.

Step 4 : Handling the PDF with Javascript

Two custom functions shown below handle most of the code.

showPDF loads the PDF. It accepts the url of the PDF as parameter. On successful loading it calls the showPage function that will show the first page of the PDF.

showPage loads and renders a specified page of the PDF. While a page is being rendered, Previous and Next buttons are disbaled. A very important point is to note that we have to change the scale of the rendered page as per the width of the canvas element. In this case the width of the canvas element is less than the actual width of the PDF, so when PDF is rendered in the canvas it has to be scaled down.

Event handlers on the Previous / Next buttons simple decrement / increment the current page shown and call the showPage function.

// Initialize and load the PDF
function showPDF(pdf_url) {
    // Show the pdf loader
    $("#pdf-loader").show();

    PDFJS.getDocument({ url: pdf_url }).then(function(pdf_doc) {
        __PDF_DOC = pdf_doc;
        __TOTAL_PAGES = __PDF_DOC.numPages;
        
        // Hide the pdf loader and show pdf container in HTML
        $("#pdf-loader").hide();
        $("#pdf-contents").show();
        $("#pdf-total-pages").text(__TOTAL_PAGES);

        // Show the first page
        showPage(1);
    }).catch(function(error) {
        // If error re-show the upload button
        $("#pdf-loader").hide();
        $("#upload-button").show();
        
        alert(error.message);
    });;
}

// Load and render a specific page of the PDF
function showPage(page_no) {
    __PAGE_RENDERING_IN_PROGRESS = 1;
    __CURRENT_PAGE = page_no;

    // Disable Prev & Next buttons while page is being loaded
    $("#pdf-next, #pdf-prev").attr('disabled', 'disabled');

    // While page is being rendered hide the canvas and show a loading message
    $("#pdf-canvas").hide();
    $("#page-loader").show();

    // Update current page in HTML
    $("#pdf-current-page").text(page_no);
    
    // Fetch the page
    __PDF_DOC.getPage(page_no).then(function(page) {
        // As the canvas is of a fixed width we need to set the scale of the viewport accordingly
        var scale_required = __CANVAS.width / page.getViewport(1).width;

        // Get viewport of the page at required scale
        var viewport = page.getViewport(scale_required);

        // Set canvas height
        __CANVAS.height = viewport.height;

        var renderContext = {
            canvasContext: __CANVAS_CTX,
            viewport: viewport
        };
        
        // Render the page contents in the canvas
        page.render(renderContext).then(function() {
            __PAGE_RENDERING_IN_PROGRESS = 0;

            // Re-enable Prev & Next buttons
            $("#pdf-next, #pdf-prev").removeAttr('disabled');

            // Show the canvas and hide the page loader
            $("#pdf-canvas").show();
            $("#page-loader").hide();
        });
    });
}

// Upon click this should should trigger click on the <input type="file" /> element
// This is better than showing the ugly looking file input element
$("#upload-button").on('click', function() {
    $("#file-to-upload").trigger('click');
});

// When user chooses a PDF file
$("#file-to-upload").on('change', function() {
    $("#upload-button").hide();

    // Send the object url of the pdf
    showPDF(URL.createObjectURL($("#file-to-upload").get(0).files[0]));
});

// Previous page of the PDF
$("#pdf-prev").on('click', function() {
    if(__CURRENT_PAGE != 1)
        showPage(--__CURRENT_PAGE);
});

// Next page of the PDF
$("#pdf-next").on('click', function() {
    if(__CURRENT_PAGE != __TOTAL_PAGES)
        showPage(++__CURRENT_PAGE);
});

Browser Compatability

The above code will work good in all major browsers, including IE 10+.

How to Open a Password Protected PDF with Javascript using PDF.JS
How to Show a Loading Progress Bar in PDF.JS