Displaying PDF files with PDF.js library

Introduction

The PDF.js library is a great open source tool created by the developers community and supported by Mozilla. It's main purpose is to display PDF files. You can display files on the canvas or use a sample viewer that converts PDF documents into DOM elements. You can also write your own viewer. In this article, we will show how to create a simple PDF viewer using canvas and PDF.js.

Prerequisites

You can download the library from this website and see an example demo here. The only two files that you need are:

  • pdf.js
  • pdf.worker.js

You have to include them in your main html document in the order as shown above.

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

Right now Tizen uses the prefixed version of the the requestAnimationFrame function (webkitRequestAnimationFrame). PDF.js uses the unprefixed version of the function to display PDF files on the canvas. So we need to provide it. We will use a very popular snippet for that, which provides cross browser support for the requestAnimationFrame method.

if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = (function() {
    return window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function(callback, element) {
            window.setTimeout(callback, 1000 / 60);
        };
    })();
}

Loading files

The PDF.js library has a built in file loader, so you don't have to worry about loading it by yourself. First of all, you have to disable the file stream reading, which is the experimental feature introduced by Mozilla.

PDFJS.disableStream = true;

The next thing is opening a PDF document. It's as simple as shown in the code below.

PDFJS.getDocument('/files/tizenfordummies.pdf');

The file loading is done using an asynchronous XMLHttpRequest. Normally we would use a callback to listen for the loaded/completed event, however in the PDF.js library we use Promises. Promises are a great way to deal with asynchronous code in a way we would have dealt like with a synchronous code. In fact, the code is still asynchronous but managing such code is much easier. You can find many great articles about the Promises. We encourage you to get familiar with this concept. However it's not needed to understand the code in this article.

So, how to respond to the loaded/completed event? Take a look at the following code:

PDFJS.getDocument('/files/tizenfordummies.pdf').then(function(pdf) {
  // Do something with the PDF file stored in the `pdf` variable
});

The function passed to the then method is executed just after a file is loaded. As a first argument of the callback function, we get a PDF file that we can display on the canvas.

Displaying a PDF file on the canvas

To display PDF file on the canvas we have to get the desired page from the pdf variable provided in the previous example. Let's see how to do it.

PDFJS.getDocument('/files/tizenfordummies.pdf').then(function(pdfFile) {
    var pageNumber = 1;
    pdfFile.getPage(pageNumber).then(function(page) {
        // Do something with the page.
    });
});

We use the getPage function from the pdfFile variable, passing page number as a first argument. And once again we use here the Promises pattern. It's not everything we have to do to display a page. We need to get the viewport to display.

PDFJS.getDocument('/files/tizenfordummies.pdf').then(function(pdfFile) {
    var pageNumber = 1;
    pdfFile.getPage(pageNumber).then(function(page) {
        var scale = 1;
        var viewport = page.getViewport(scale);
    });
});

As you can see, we can pass the scale parameter to the getViewport function of the page variable. The default value of this parameter is 1. Now we can display the page on the canvas.

var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');

var renderContext = {
    canvasContext: context,
    viewport: viewport
};

page.render(renderContext);

We use the render function of the page object to render the given page. We have to pass some additional data. It's the canvas rendering context and viewport that will scale the page to the proper size (if desired).

That's all you have to do to display PDF file on the canvas.

Sample application

Let's move on to a more useful example. We will describe the process of creating a simple PDF viewer with zooming and page changing, using the swipe gesture.

You can download a sample application from the link at the end of this article.

Our index.html file is very simple. We have just one div container, canvas element and three javascript files at the end of the body tag (to load them after all the content of the page had been loaded).

<body>
    <div id="page">
        <canvas id="canvas"></canvas>
    </div>
    
    <script src="js/pdf.js"></script>
    <script src="js/pdf.worker.js"></script>
    <script src="js/main.js"></script>
</body>

All the code that we will be describing resides in the main.js file.

First, we will describe a function related directly to the displayed PDF files. We've created the openPage helper function that takes as a first argument a PDF file and the page number as a second.

var zoomed = false;
var openPage = function(pdfFile, pageNumber) {
    pdfFile.getPage(pageNumber).then(function(page) {
        viewport = page.getViewport(1);

        if (zoomed) {
            var scale = pageElement.clientWidth / viewport.width;
            viewport = page.getViewport(scale);
        }

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

        var renderContext = {
            canvasContext: context,
            viewport: viewport
        };

        page.render(renderContext);
    });
};

Most of the code should be familiar for you, but there are some thingd that need explanation. We've introduced the zoomed variable that got changed after double clicking the application screen (zoom out). Another double click zooms in the document. To calculate the scale factor, we divide the page div container element width by the viewport width.

We also created two helper functions for changing the page.

var currPageNumber = 1;

var openNextPage = function() {
    var pageNumber = Math.min(pdfFile.numPages, currPageNumber + 1);
    if (pageNumber !== currPageNumber) {
        currPageNumber = pageNumber;
        openPage(pdfFile, currPageNumber);
    }
};

var openPrevPage = function() {
    var pageNumber = Math.max(1, currPageNumber - 1);
    if (pageNumber !== currPageNumber) {
        currPageNumber = pageNumber;
        openPage(pdfFile, currPageNumber);
    }
};

We just check if new incremented/decremented value of the currPageNumber variable is greater than 1 and smaller then pages count (pdfFile.numPage property). We also check if the page number differs after incrementation/decrementation, because we don't want to render the entire page if it's not needed. The last thing we do is setting the new page number and displaying the page using the openPage function.

Now, we will move on to the navigation part of the application. We will implement the double click and swipe events.

Double Click Event

var zoomed = false;
var toggleZoom = function() {
    zoomed = !zoomed;
    openPage(pdfFile, currPageNumber);
};

var lastTouchTime = 0;
pageElement.addEventListener('touchstart', function(e) {
    touchDown = true;

    if (e.timeStamp - lastTouchTime < 500) {
        lastTouchTime = 0;
        toggleZoom();
    } else {
        lastTouchTime = e.timeStamp;
    }
});

We've created the lastTouchTime variable that stores time of the last touchstart event. If the difference between the current event time and the last one is lower than 500 ms, then we run the toogleZoom function and of course reset the lastTouchTime variable value.

The toggleZoom function just negates the value of the zoomed flag and uses the openPage method to display the current page.

Swipe Gestures

Swipe gesture are a little bit more complicated. We have to handle two touch events. The touchmove and touchend.

pageElement.addEventListener('touchmove', function(e) {
    if (pageElement.scrollLeft === 0 ||
        pageElement.scrollLeft === pageElement.scrollWidth - page.clientWidth) {
        reachedEdge = true;
        if (touchStart === null) {
            touchStart = e.changedTouches[0].clientX;
        }
    } else {
        reachedEdge = false;
        touchStart = null;
    }

    if (reachedEdge && touchStart) {
        var distance = e.changedTouches[0].clientX - touchStart;
        if (distance < -100) {
            touchStart = null;
            reachedEdge = false;
            openNextPage();
        } else if (distance > 100) {
            touchStart = null;
            reachedEdge = false;
            openPrevPage();
        }
    }
});

In the touchmove event we check if the scroll position of our page container element is in one of the two extremes in the horizontal axis. The lowest value of the scroll position is 0 and the highest is equal to the pageElement.width - element width. If we reached one of those values, we set the reachedEdge variable and save the x position of the touch point in the touchStart variable. If we haven't reached the edge, we reset the touchStart variable.

In the next step, we check if both the reachedEdge and touchStart variables are set. When the condition is fulfilled, then we check if the absolute distance between the touch start and current x position of the touch point is greater than 100. When it's -100, it means that we want to open the next page, if +100 then previous one. We can change multiple pages by making longer swipes. Any multiplication of 100 pixels distance in the horizontal axis will result in moving back and forth by appropriate page number.

pageElement.addEventListener('touchend', function(e) {
    touchStart = null;
});

In the touchend event, we just reset the touchStart variable, to start the whole process from the beginning.

Summary

You can download the sample application attached to this article and investigate the code in details. PDF.js library has much more functions to get familiar with, so we encourage you to take a close look at the code of the library. Documentation is still a work in progress. Hope this article will help you create application to manage PDF files.

File attachments: 
List
SDK Version Since: 
2.3.0