Creating PDF documents with jsPDF

Introduction

In the previous article Displaying PDF files with PDF.js library we have showed how to display PDF files on the HTML canvas. In this article, we will show how to create PDF files from scratch. We will use jsPDF library for this purpose. You can download the newest library version just from the GitHub repository or from the official website. Unfortunately, the documentation for the library is poor, so we will describe most important APIs.

Prerequisites

We have to download the newest version of the libarary and include it in the HEAD or at the end of the BODY section.

<head>
    <script src="js/jspdf.js"></script>
</head>
<body>
    <!-- Your code goes here -->
    
    <script src="js/jspdf.js"></script>
    <script src="js/main.js"></script>
</body>

Having done that, we can start using the jsPDF library.

Example application

We will describe the process of generating PDF documents using our sample application attached to this article. We will go through examples and discuss all the used methods.

The sample application consists of just few buttons, where clicking each button generates a different PDF file. We've created few helper methods that will deal with the process of responding to the user actions and saving files on the device. Let's examine them.

At first, we attach a saveExample function to each button. This function will be invoked after clicking the button. The function takes an example number as the only parameter.

var examples = document.querySelectorAll('button');
Array.prototype.forEach.call(examples, function(example) {
    example.addEventListener('click', function() {
        saveExample(parseInt(example.dataset.example, 10));
    });
});

Inside the function, we check whether the example number has the correct value and ask the user to name the file.

var saveExample = function(example) {
    if (example < 1 || example > 5) return;

    var fileName = prompt('How to name the example PDF file?');
    if (!fileName || fileName.length === 0) return;

    if (fileName.substr(-4, 4).toLowerCase() !== '.pdf') {
        fileName += '.pdf';
    }

    tizen.filesystem.resolve('documents', function(dir) {
        var file = dir.createFile(fileName);
        file.openStream('w', function(stream) {
            getExampleOutput(example, function(output) {
                var chunk = 16384;
                for (var beg = 0; beg < output.length; beg += chunk) {
                    stream.writeBytes(Array.apply(null, output.subarray(beg, beg + chunk)));
                }
                stream.close();
            });
        }, errorHandler);
    }, errorHandler);
};

If the user forgets to pass the file name or click cancel, then we stop executing the function. If the file name does not contain the .pdf suffix, we add it. Next, we use the Tizen filesystem API for accessing the documents directory, creating the file and filling it with data. To use the filesystem API you have to add http://tizen.org/privilege/filesystem.read and http://tizen.org/privilege/filesystem.write privileges in the config.xml file.

We resolve the document directory using the tizen.filesystem.resolve method and passing the 'document' string as the first parameter. The second parameter is a callback function that will be executed when the process of resolving the directory is finished. As the first parameter of the callback function, we get the directory object that points to the documents folder. Now, we can create a file using the dir.createFile() method. The only parameter that we need to pass in is the file name. It returns an object pointing to the just created file.

Now, we have to fill our file with the data. We open a stream for that purpose using the file.openStream. The stream will be used for writing data, so the first parameter is the "w" string. The second parameter is a callback function that will be executed after the stream is opened. The only parameter of the callback function is the stream object. Now we can save some data using the write method of the stream object. When, we are finished with saving the data, we have to close the stream using the stream.close() method.

Now there is only one missing element. How to get the PDF file data?

var getExampleOutput = function(example, callback) {
    var pdfDoc = new jsPDF();

    switch (example) {
        case 1:
            callback(new Uint8Array(pdfDoc.output('arraybuffer')));
            break;
        case 2:
            break;
        case 3:
            break;
        case 4:
            break;
        case 5:
            break;
    }
};

As you can see, we use the getExampleOutput function to get data of the PDF file. We pass an example number as the first parameter. The getExampleOutput function is where all our work with generating the PDF file is done. Right now, it's worth mentioning that PDF objects created with jsPDF library have a save method that does not work in Tizen, because it uses the download attribute of the A tag that has not been implemented in Tizen yet. So we have to get a pure PDF file data (ArrayBuffer converted to the Uint8Array view) and save it by our selves. To get output as ArrayBuffer we pass the "arraybuffer" string as the first argument of the pdfDoc.output method. Later we wrap the ArrayBuffer with the Uint8Array view. We have to create that view, so that the buffer will be interpreted as individual bytes.

Having the PDF file data as Uint8Array, we loop over that buffer and write each chunk into the stream. We have to convert chunks to simple Arrays because it's the only accepted format by the writeBytes method of the stream object.

The jsPDF library APIs

Creating a document

First let us discuss how to create a new document. It's as simple as executing this code.

var doc = new jsPDF(orientation, unit, format, compress);

The constructor can take several parameters.

  • orientation - The default value for orientation is "portrait". We can set it to "landscape" if we want a different page orientation.
  • unit - We can tell jsPDF in which units we want to work. Use one of the following: "pt" (points), "mm" (default), "cm", "in".
  • format - It's default page format. It can be "a3", "a4" (default), "a5", "letter", "legal".

We can add new page using the following code.

doc.addPage(width, height);

As parameters we pass the page, width and height in the units defined in the document constructor. Adding pages moves us to this page, so any operation will be executed on that page. If we want to go to another page we can use the setPage function.

doc.setPage(pageNumber);

You can also get actual page numbers by using this code.

doc.internal.getNumberOfPages();

The first example in the sample application demonstrates the usage of the functions described above. You can run the application to check it out and investigate the application's code that is attached to the article.

Working with text

First, the most important thing is displaying text, we do it using the doc.text function which takes 3 parameters. The first two are X and Y positions of the text in units defined in the document constructor. Notice that the Y position, is the position of the text baseline, so printing something with the Y position set to 0 will actually print it over the top edge of the document. The third argument is the text to be displayed.

doc.text(10, 10, "Hello world!");

The second thing is the font name used to draw the text. We can choose one of the following: courier, helvetica, time. We change the font family and font style by running the doc.setFont function.

doc.setFont("courier", "italic");

By executing the doc.getFontList function we can find out what fonts are available and what font styles we can set for given font.

doc.getFontList();
/*
{
    "helvetica": ["normal", "bold", "italic", "bolditalic"],
    "Helvetica": ["", "Bold", "Oblique", "BoldOblique"],
    "courier": ["normal", "bold", "italic", "bolditalic"],
    "Courier": ["", "Bold", "Oblique", "BoldOblique"],
    "times": ["normal", "bold", "italic", "bolditalic"],
    "Times": ["Roman", "Bold", "Italic", "BoldItalic"]
}
*/

We can also change font styles individually thanks to the doc.setFontStyle or the doc.setFontType function, which is the alias to the first one.

doc.setFontType("bolditalic");
// is the same as calling
doc.setFontStyle("bolditalic");

Next thing is the font size. It's as simple as calling the doc.setFontSize function.

doc.setFontSize(40);

The last thing is the text color. We change text color using the doc.setTextColor function and passing three parameters which are RGB (Red, Green, Blue) color values.

doc.setTextColor(255, 0, 0);

The sample document showing different ways of displaying text is located under Example 2 in the sample application attached to the article.

Working with images

The only function for images is the doc.addImage. It takes image as a first parameter, image format/type as a second and X, Y positions of the image as a third and fourth arguments. We can also optionally pass new image size as a fifth and sixth argument.

var img = new Image();
img.addEventListener('load', function() {
    var doc = new jsPDF();
    doc.addImage(img, 'png', 10, 50);
});
img.src = 'images/tizen.png';

In the example above, we passed an Image HTML DOM element as a first argument of the addImage function, however it can also be a base64 encoded image string. For now, the only supported image formats are jpeg/jpg and png.

Working with graphics

First, we have to set the drawn shapes fill and stroke colors. We do it using the doc.setFillColor and the doc.setDrawColor accordingly, passing RGB color values as parameters.

doc.setFillColor(100, 100, 240);
doc.setDrawColor(100, 100, 0);

We can also set the stroke width. The stroke width unit is the same as defined in the document constructor.

doc.setLineWidth(1);

Every shape drawing function takes the center point coordinates (triangle is the only exception) as two first parameters. They also take the last parameter drawing style. It can be "S", "F", "DF", "FD" string and the meanings are: "stroke", "fill", "stroke and fill", "fill and stroke". The last two of course differ in the order of the drawing operations.

We can draw an ellipse, by passing two radiuses...

// Empty ellipse
doc.ellipse(50, 50, 10, 5);
// Filled ellipse
doc.ellipse(100, 50, 10, 5, 'F');
// Filled circle with borders

... or a circle, by passing only one radius...

doc.circle(150, 50, 5, 'FD');

... or a rectangle, by passing its width and height...

// Empty square
doc.rect(50, 100, 10, 10);
// Filled square
doc.rect(100, 100, 10, 10, 'F');
// Filled square with borders
doc.rect(150, 100, 10, 10, 'FD');

... a rounded rectangle, by passing its width, height and border radiuses...

// Filled sqaure with rounded corners
doc.roundedRect(50, 150, 10, 10, 3, 3, 'FD');

... and a triangle, by passing each corners coordinates.

// Filled triangle with borders
doc.triangle(50, 200, 60, 200, 55, 210, 'FD');

We can also draw lines passing through the coordinates of two points.

// Line
doc.line(50, 250, 100, 250);

Summary

In this article we've described the process of creating PDF documents using the jsPDF library. It's an easy and powerful tool and is still under development, so new great features are expected to emerge soon.

File attachments: 
List
SDK Version Since: 
2.3.0