Generating PDF Files in Node.js with PDFKit

Introduction

The PDF format is one of the most common document formats for transferring information. In dynamic web applications, you might need to export data into a document and PDF is usually a popular option. In this article, we will discuss how to generate PDF files in NodeJS using the NPM package pdfkit.

PDFKit is a JavaScript PDF generation library for Node.js that provides an easy way to create multi-page, printable PDF documents.

Getting Started With PDFKit

Let's create a project directory, cd into it and initialize the Node project with default settings:

$ mkdir pdfkit-project
$ cd pdfkit-project
$ npm init -y

Then, let's install pdfkit:

$ npm install pdfkit

To use the module in the project, we'll import it via require():

const PDFDocument = require('pdfkit');

Creating a PDF Document using PDFKit

To create a PDF document, we will need to import the fs (file system) module as well. We'll pipe the contents of our PDF file into a fs's writeable stream to save it. Let's take a look at how to do that:

const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('SampleDocument.pdf'));
pdfDoc.text("My Sample PDF Document");
pdfDoc.end();

First, we import the required modules, after which, we instantiate the PDFDocument. This instance is a readable stream. We'll be piping that stream into a writeable stream to save the file.

If you're not familiar with how streams work, check out our Introduction to Node.js Streams.

We're using the pipe() function to do this and save the resulting SampleDocument.pdf into our root directory. Once created, we can add contents to it, via the text function. Of course, we'll want to end() the stream in the end.

When we run the code, a PDF file called SampleDocument.pdf is created in the root folder of our project:

$ node index.js

Note: Before attempting to overwrite an existing PDF file, it must be free. I.e - all windows with that PDF file must be closed or the program will throw an error.

Formatting Text in PDF File

Of course, pdfkit allows us to do much more than just add unformatted text to a document. Let's take a look at some of the features it offers.

Positioning Text

By default, the pdfkit module keeps track of where text should be added to the document, essentially print each call to the text() function in a new line.

You can change where the text is printed within the current page, by adding the x and y coordinates of the location where you want the text to be placed as arguments to the text() function.

For example:

pdfDoc.text("Text positioned at (200,200)", 200, 200);

This is useful because it allows you to fine-tune the positioning of text, especially since PDF documents have a universal look regardless of the machine/OS they are opened on. This would also allow you to, for example, print text over other text:

const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('SampleDocument.pdf'));

pdfDoc.text("From Mon-Sat we will have a 10% discount on selected items!", 150, 150);
pdfDoc
    .fillColor('red')
    .fontSize(17)
    .text("20%", 305, 150);

pdfDoc.end();

Running this code would give us:

text positioning

Text Wrapping and Alignment

The pdfkit module automatically wraps lines so that they fit between the margins, or in the width provided (when writing text in columns). In other words, the lineBreak option is true by default. You can change it to false when calling the text() function:

pdfDoc.text("very long text ".repeat(20), { lineBreak : false });

New pages are also added automatically as needed, i.e. as soon as the content you want to add doesn't fit on the current page in its entirety. However, you can also switch to the next page before filling out the previous one by simply calling:

pdfDoc.addPage();

As for alignment, pdfkit provides us with the usual options - left (default), right, center and justify. Note that setting a specific alignment with lineBreak set to false will not work, even if the text can fit in a line.

Just as lineBreak, the align parameter is set by passing an object containing key-value pairs to the text() function. Let's look at some alignment examples:

const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('text_alignment.pdf'));

pdfDoc.text("This text is left aligned", { align: 'left'})
pdfDoc.text("This text is at the center", { align: 'center'})
pdfDoc.text("This text is right aligned", { align: 'right'})
pdfDoc.text("This text needs to be slightly longer so that we can see that justification actually works as intended", { align: 'justify'})

pdfDoc.end();

Running the code above would give us a PDF that looks like this:

text alignment

Styling Text

The pdfkit module also provides options that can be used to style text in your PDF documents. We will take a look at some of the more important styling options, you can find the full list of options in the PDF Guide.

We can pass different options as key-value pairs to the text() function, and also chain several other functions before calling text() at all.

A very important thing to note is that chained functions, such as fillColor() (and later font(), fontSize(), etc.) will impact all of the text after that call:

const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('text_styling.pdf'));

pdfDoc
    .fillColor('blue')
    .text("This is a link", { link: 'https://pdfkit.org/docs/guide.pdf', underline: true });
pdfDoc
    .fillColor('black')
    .text("This text is underlined", { underline: true });
pdfDoc.text("This text is italicized", { oblique: true });
pdfDoc.text("This text is striked-through", { strike: true });

pdfDoc.end();

Running this code will generate a PDF file with the following contents:

text styling

Changing styles in the middle of a paragraph is slightly more complicated, since chaining multiple text() functions adds a new line after each by default. We can avoid this by setting the lineBreak option of the first text() call to false:

const PDFDocument = require('pdfkit');
const fs = require('fs');

var pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('text_styling2.pdf'));

pdfDoc
    .fillColor('blue')
    .text("This text is blue and italicized", {oblique : true, lineBreak : false})
    .fillColor('red')
    .text(" This text is red");

pdfDoc.end();

Which would give us the desired result:

text styling same paragraph

Creating Lists

To add a list of items in your PDF document, the PDFDocument instance has a list() function that takes in an array of string items (or nested arrays of strings) and displays them as a bullet list:

const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('lists.pdf'));

let myArrayOfItems = ['Item 1', 'Item 2', 'Item 3', 'Item 4'];

pdfDoc.list(myArrayOfItems);
// Move down a bit to provide space between lists
pdfDoc.moveDown(0.5);

let innerList = ['Nested Item 1', 'Nested Item 2'];
let nestedArrayOfItems = ['Example of a nested list', innerList];

pdfDoc.list(nestedArrayOfItems);

pdfDoc.end();

Which gives us:

lists with pdfkit

Fonts

PDFKit comes with 14 standard fonts that can be used in PDF documents. Any of these fonts can be passed to the font() function of the PDFDocument class, and chained with text():

pdfDoc.font('Times-Roman').text('A text in Times Roman')

You can also add additional fonts by passing the path to the font file as an argument to the font() function, as well as the name of the specific font you want in case the file has a collection of fonts. Alternatively, you can give the new font a name so that it can be accessed by that name instead of the file path:

pdfDoc.registerFont('Name of the font', '/file_path', 'specific_font_name_in_case_of_a_collection')

Calls to font() can be chained with other functions, just as in the fillColor() example.

You can also set the font size by using the fontSize() function. Let's take a look at some examples:

const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('fonts.pdf'));

pdfDoc.font('ZapfDingbats').text('This is a symbolic font.');
pdfDoc.font('Times-Roman').fontSize(25).fillColor('blue').text('You can set a color for any font');
pdfDoc.font('Courier').fontSize(5).fillColor('black').text('Some text to demonstrate.');

pdfDoc.end();

Running this would give us the following PDF as an output:

fonts with pdfkit

Adding Images

Another common thing you might want to add to your PDF files is images. You can call the image() function on the document instance and pass the path or the URI of the image you want to include.

You can also set options like the width, height, horizontal and vertical alignment of the image by passing an object containing key-value pairs as an argument to the image() function. By default, images are loaded in their original size.

If you set the width and height - the image will be stretched to fit within the specified parameters. If one of these is omitted, the image is scaled proportionally to the provided parameter:

const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('images.pdf'));

pdfDoc.text('By default, the image is loaded in its full size:')
pdfDoc.image('raspberries.jpg');

pdfDoc.moveDown(0.5)
pdfDoc.text('Scaled to fit width and height')
pdfDoc.image('raspberries.jpg', {width: 150, height: 150});

pdfDoc.moveDown(0.5)
pdfDoc.text('Scaled to fit width')
pdfDoc.image('raspberries.jpg', {width: 150});

pdfDoc.end();

Running this code would give us:

default and scaled images

You can also scale the image by giving a scale factor. Additionally, you can give a fit or cover array, where the image will be scaled to either fit the provided rectangle, or cover it, respectively. If you do provide a fit or cover array, you can also set the horizontal alignment (align) and the vertical alignment (valign):

const PDFDocument = require('pdfkit');
const fs = require('fs');

let pdfDoc = new PDFDocument;
pdfDoc.pipe(fs.createWriteStream('images.pdf'));

pdfDoc.text('Scaled by a factor, keeps the original proportions:')
pdfDoc.image('raspberries.jpg', {scale: 0.75});

pdfDoc.moveDown(0.5)
pdfDoc.text('Fit with horizontal alignment:')
pdfDoc.image('raspberries.jpg', {fit: [400, 150], align: 'center'});

pdfDoc.end();

This would give us:

scale factor and fit

Conclusion

In this article we have seen how to generate PDF files in Node.js using PDFKit. We have explored some of the options available for formatting text and how to add images to our files. The library has extensive documentation that covers a lot more on creating PDF files in Node.js applications.