Working with PDF in Node.JS (pdfmake)

Are you trying to work with PDF in Nodejs? In this article, we will discuss the package pdfmake. pdfmake is a library that can be used for server-side and client-side to create pdfs, which is purely written in JavaScript. We will get started with pdfmake, and create a basic pdf file.

Let's get started.

We will be working on server-side, so let's initialize a Node project.


:~/working-with-pdf$ npm init -y

Let's create an index.js file, and update the package.json by adding the start script.

Now, Let's install the pdfmake 


:~/working-with-pdf$ npm install pdfmake

Before getting started, we will need fonts. By default, pdfmake will look for 'Roboto' in style 'normal'. So let's download the fonts. [Roboto on Google Fonts]. Once downloaded, extract the zip and place the font files on font/roboto/

Let's define the fonts


var fonts = {
Roboto: {
normal: 'fonts/roboto/Roboto-Regular.ttf',
bold: 'fonts/roboto/Roboto-Medium.ttf',
italics: 'fonts/roboto/Roboto-Italic.ttf',
bolditalics: 'fonts/roboto/Roboto-MediumItalic.ttf'
}
};

Now we will instantiate the pdfmake, define a document, pass the defined doc to createPdfKitDocument to get PdfKit instance.  Create a stream and pipe it to pdfKit instance.


const fs = require('fs');

const Pdfmake = require('pdfmake');


var fonts = {
Roboto: {
normal: 'fonts/roboto/Roboto-Regular.ttf',
bold: 'fonts/roboto/Roboto-Medium.ttf',
italics: 'fonts/roboto/Roboto-Italic.ttf',
bolditalics: 'fonts/roboto/Roboto-MediumItalic.ttf'
}
};


let pdfmake = new Pdfmake(fonts);

let docDefination = {
content: [
'Hello World!'
],
}


let pdfDoc = pdfmake.createPdfKitDocument(docDefination, {});
pdfDoc.pipe(fs.createWriteStream('pdfs/test.pdf'));
pdfDoc.end();

Make sure you have created that pdfs/ directory first.

createPdfKitDocument will return the instance of pdfKit, which is a readable Node stream. They don't get saved anywhere automatically. So we call the pipe method to send the output of the document to another writeable Node stream. We can pipe it to HTTP response as well.

Now the pdf is created.


Header and Footer

We can define a header as well as a footer with pdfmake, all the pages the content extended to will have the header and footers.

We will use an image on the header as well, so let's create a images/ directory and place an image there. Here we have info.png on images/



let content = [{
text: "Hello World",
alignment: 'center',
fontSize: 25
}]

for (let i = 0; i < 50; i++) {
content.push({
text: `${i}) a random test.. `
})
}

let headerfooterDoc = {
header: {
margin: [0, 20, 0, 0],
alignment: 'center',

image: 'images/info.png',
height: 100

},
footer: {
margin: [72, 0, 72, 0],
fontSize: 10,
columns: [{
with: 'auto',
alignment: 'left',
text: '© Blogger Nepal 2022'
}

],
},
content: content,
pageMargins: [72, 120, 72, 50],
}

pdfDoc = pdfmake.createPdfKitDocument(headerfooterDoc, {});
pdfDoc.pipe(fs.createWriteStream('pdfs/headerfooter.pdf'));
pdfDoc.end();

Here is the output pdf.


Dynamic Content on Header and Footer

Let's include the page number on the footer. We will modify the footer on headerfooterDoc in the above document.


footer: (currentPage, pageCount, pageSize) => {
return {
margin: [72, 0, 72, 0],
fontSize: 10,
columns: [{
with: 'auto',
alignment: 'left',
text: '© Blogger Nepal 2022'
},
{
with: 'auto',
alignment: 'right',
// text: 'Page | 1'
text: [{
color: '#7f7f7f',
text: 'Page | '
},
{
text: currentPage
}
]
}

],
}
},

That's it, now we have a page number on all pages at the right of the footer. 

Styling

We have already used some styling. We have used inline styling whenever needed. In pdfmake we can define style and reuse as required. We will define some styles and create pdf.


let stylingDocs = {
content: [{
text: "Report",
style: 'header'
}, {
text: 'This is an anual report. bla bla, some text goes here about this and that',
style: 'text'
}, {
text: "About Blogger Nepal",
style: 'subheader'
}, {
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse porta, lectus ac pellentesque suscipit, lacus felis cursus nisi, ac dictum velit ipsum ac dolor. Ut erat eros, dictum nec porttitor non, rhoncus sed augue. Maecenas consectetur aliquam lorem. Duis id congue risus. Ut vel rutrum massa, eget convallis nulla. Praesent ac sagittis diam. Aliquam erat volutpat. Fusce posuere sapien vitae suscipit interdum. Curabitur in mattis massa, vel aliquet ex. Vestibulum ornare lorem et sollicitudin commodo. Sed id vestibulum ligula. ',
style: 'text'
}, {
text: '\n'
}, {
text: 'Nunc laoreet mauris sed vestibulum pellentesque. Nunc suscipit ante placerat orci pulvinar varius. Sed in velit maximus odio rutrum molestie et a enim. Mauris vitae dui rutrum tortor euismod tempor in eu diam. Fusce pulvinar nunc at feugiat pharetra. Morbi tempus accumsan finibus. Suspendisse dictum augue eu finibus volutpat. Suspendisse vel ipsum quis eros tincidunt posuere id eu nunc. Sed cursus sodales accumsan. Sed interdum dolor ac dignissim eleifend. ',
style: 'text',

}, {
text: [
'\nThe output of the code was ',
{
bold: true,
fontSize: 15,
text: "Hello World!"
},
" It worked. as expected"
]
}],
styles: {
header: {
fontSize: 18,
bold: true,
alignment: 'center',
margin: [0, 30, 0, 20]
},
subheader: {
fontSize: 14,
margin: [0, 15, 0, 10],
color: '#003893',
},
text: {
alignment: 'justify'
}
}
}

pdfDoc = pdfmake.createPdfKitDocument(stylingDocs, {});
pdfDoc.pipe(fs.createWriteStream('pdfs/styling.pdf'));
pdfDoc.end();

Here is the output pdf.


List and Table with pdfmake

Let's work with lists and tables. We will use some links as well.


let listTableDocs = {
content: [{
text: "About Blogger Nepal",
style: 'subheader'
}, {
ul: [{
text: 'Nepali Calendar',
link: 'http://calendar.bloggernepal.com'
},
'Blog',
'tools'
]
}, {
text: '\n\n'
}, {
ol: [{
text: 'Is this a Word?',
style: 'link',
link: 'https://isthisaword.bloggernepal.com/'
},
{
text: 'Countries with State and Dialcode',
style: 'link',
link: 'https://countries.bloggernepal.com/'
},
{
text: 'SocketIO Client Online',
style: 'link',
link: 'https://socketio.bloggernepal.com/'
},
{
text: 'Bunny Jump',
style: 'link',
link: 'https://bunnyjump.bloggernepal.com/'
},
]
}, ],
styles: {
header: {
fontSize: 18,
bold: true,
alignment: 'center',
margin: [0, 30, 0, 20]
},
subheader: {
fontSize: 14,
margin: [0, 15, 0, 10],
color: '#003893',
},
text: {
alignment: 'justify'
},
link: {
decoration: 'underline',
color: '#0074c1'
}
}


}


let table = {
// headers are automatically repeated if the table spans over multiple pages
// you can declare how many rows should be treated as headers
headerRows: 3,
widths: ['*', 'auto', 100, 60, 50, 60, 50],

body: [
[{
text: 'Name',
rowSpan: 3
}, {
text: 'Age',
rowSpan: 3
}, {
text: 'Gender',
rowSpan: 3
}, {
text: 'Mark',
alignment: 'center',
colSpan: 4
}, {}, {}, {}],
[{}, {}, {}, {
text: 'First Year',
alignment: 'center',
colSpan: 2
}, {}, {
text: 'Second year',
alignment: 'center',
colSpan: 2
}, {}],
[{}, {}, {}, {
text: 'Theory',
}, {
text: 'Practical'
}, {
text: 'Theory',
}, {
text: 'Practical',
}],
// now data and values
['Ram', '32', 'Male', '90', '95', '80', '95'],
['Sita', '30', 'Female', '95', '95', '80', '95'],
['Laxman', '26', 'Male', '70', '90', '75', '90'],
]
}

listTableDocs['content'].push({
text: "Table Now",
style: 'subheader'
}, {
table: table
})

pdfDoc = pdfmake.createPdfKitDocument(listTableDocs, {});
pdfDoc.pipe(fs.createWriteStream('pdfs/listtable.pdf'));
pdfDoc.end();


Here is the output of the above code.

Wait for Write to Complete

Now, we have created the file, but how do we use it? It won't be available right away. We should make a way when we are notified once the write is completed. We can achieve this by listening to the finish event on writestream.


pdfDoc = pdfmake.createPdfKitDocument(listTableDocs, {});

let writeStream = fs.createWriteStream('pdfs/listtable.pdf');

pdfDoc.pipe(writeStream);
pdfDoc.end();

writeStream.on('finish', function () {
// do stuff here that need to be after the pdf write is completed

// sending to frontend
// sending as attachment
});

Or we can create a method that will return a promise and will be resolved once the write is complete, we will use the same finish event on the stream

Get the Sourcecode on this Github Repository.


const makePDF = (datas) => {
let aPromise = new Promise((resolve, reject) => {
console.time('creatingPDF')

/// all those stuffs

let docDefination = {
content: [
'Hello World!'
],
}


let pdfDoc = pdfmake.createPdfKitDocument(docDefination, {});

let writeStream = fs.createWriteStream('pdfs/test.pdf');

pdfDoc.pipe(writeStream);
pdfDoc.end();

writeStream.on('finish', function () {
console.timeEnd('creatingPDF')
resolve('pdfs/test.pdf');
});

})

return aPromise;
}

makePDF({
data: "asd needed"
}).then(file => {
console.log(file);
})

That's it we worked with pdfmake and created several pdf files. Hope you get some understanding on pdfmake, and can work with it as required.


If you are trying to work with pdf in Node.JS, the module you must have to remember is pdfmake. This module helps a lot while working with pdf. pdfMake is a package that helps in generating pdf. files. This package is purely written in JavaScript and can be directly used on the client-side or on the server-side via node.

For the Client version to install you need to type:

bower install pdfmake


For the Server version in the terminal you need to type:

npm install pdfmake


If only want to use pdfMake on the client-side vfs_fonts.js is required for fonts. The file is a binary which includes the fonts.

PdfMake helps to print pdf's directly in the browser or delegate it to NodeJs backend using the same document definition in both cases.

Once you have pdfMake no worry about the manual calculation of x,y positioning etc. Just you have to declare document structure and let pdfmake do the rest.

You can declare your own styles, use custom fonts, build a DSL and extend the framework. Using paragraphs, columns, lists, tables, canvas, etc. customizing and styling document structure.

2 Comments

  1. How to add base64 images

    ReplyDelete
  2. Nice, concise and very helpful article.

    ReplyDelete