Client/server side PDF printing in pure JavaScript
Check out the playground and examples.
- line-wrapping,
- text-alignments (left, right, centered, justified),
- numbered and bulleted lists,
- tables and columns
- auto/fixed/star-sized widths,
- col-spans and row-spans,
- headers automatically repeated in case of a page-break,
- images and vector graphics,
- convenient styling and style inheritance,
- page headers and footers:
- static or dynamic content,
- access to current page number and page count,
- background-layer,
- page dimensions and orientations,
- margins,
- custom page breaks,
- font embedding,
- support for complex, multi-level (nested) structures,
- table of contents,
- helper methods for opening/printing/downloading the generated PDF,
- setting of PDF metadata (e.g. author, subject).
Here's a list of all added/changed features in this version of PdfMake:
An image can now be positioned vertically by using the verticalAlignment attribute.
Currently it can only be centered, either in itself or inside a fit-box.
This would put the picture 50px down inside the center of the fit-box:
{
image: 'image.png', // size 200x50
fit: [200, 100],
verticalAlignment: 'center',
}
This would move the picture 25px down to its own center from whatever position it was:
{
image: 'image.png', // size 200x50
verticalAlignment: 'center',
}
If the hLineWhenBroken flag inside the table layout is set to true, the hLineWidth/hLineColor are also called when a page breaks.
These events fire before the break happens, so that a line can be drawn at the end of the current page.
table: {
dontBreakRows: true,
body: [...]
},
layout: {
hLineWhenBroken: true,
hLineWidth: (rowNum, node) => ...
hLineColor: (rowNum, node) => ...
}
Text can now be measured before creating an actual document.
For that you need to initialize PdfMake beforehand.
const pdfMake = new PdfMake(fontData).initDocument();
After that, the text can be measured (the second argument for styling information is optional):
const textSize = pdfMake.calculateTextSize("SomeTextToMeasure", {
font: "TheFontName",
fontSize: 6.5
});
// example result:
// {
// width: 146.263,
// height: 8.853000000000002,
// fontSize: 6.5,
// lineHeight: 1,
// ascender: 6.948499999999999,
// descender: -1.9044999999999999
// }
This document will walk you through the basics of pdfmake and will show you how to create PDF files in the browser. If you're interested in server-side printing check the examples folder.
To begin with the default configuration, you should include two files:
- pdfmake.min.js,
- vfs_fonts.js - default font definition (it contains Roboto, you can however use custom fonts instead)
<!doctype html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<title>my first pdfmake example</title>
<script src='build/pdfmake.min.js'></script>
<script src='build/vfs_fonts.js'></script>
</head>
<body>
...
You can get both files using npm (server-side and client-side):
npm install pdfmake
- for server-side use
require('pdfmake');
- for client-side use
require('pdfmake/build/pdfmake.js');
andrequire('pdfmake/build/vfs_fonts.js');
or bower (client-side):
bower install pdfmake
or copy them directly from the build directory from the repository. Otherwise you can always build it from sources.
See issue.
pdfmake follows a declarative approach. It basically means, you'll never have to calculate positions manually or use commands like: writeText(text, x, y)
, moveDown
etc..., as you would with a lot of other libraries.
The most fundamental concept to be mastered is the document-definition-object which can be as simple as:
var docDefinition = { content: 'This is an sample PDF printed with pdfMake' };
or become pretty complex (having multi-level tables, images, lists, paragraphs, margins, styles etc...).
As soon as you have the document-definition-object, you're ready to create and download/open/print the PDF:
pdfMake.createPdf(docDefinition).download();
pdfMake.createPdf(docDefinition).open();
pdfMake.createPdf(docDefinition).print();
Details in the next chapters.
pdfMake.createPdf(docDefinition).download();
Parameters:
defaultFileName
(optional) - file namecb
(optional) - callback functionoptions
(optional)
pdfMake.createPdf(docDefinition).open();
Parameters:
options
(optional)win
(optional) - window (when an asynchronous operation)
Name can be defined only by using metadata title
property (see Document metadata).
Asynchronous example:
$scope.generatePdf = function() {
// create the window before the callback
var win = window.open('', '_blank');
$http.post('/someUrl', data).then(function(response) {
// pass the "win" argument
pdfMake.createPdf(docDefinition).open({}, win);
});
};
Open in same window:
pdfMake.createPdf(docDefinition).open({}, window);
pdfMake.createPdf(docDefinition).print();
Parameters:
options
(optional)win
(optional) - window (when an asynchronous operation)
Asynchronous example:
$scope.generatePdf = function() {
// create the window before the callback
var win = window.open('', '_blank');
$http.post('/someUrl', data).then(function(response) {
// pass the "win" argument
pdfMake.createPdf(docDefinition).print({}, win);
});
};
Print in same window:
pdfMake.createPdf(docDefinition).print({}, window);
const pdfDocGenerator = pdfMake.createPdf(docDefinition);
pdfDocGenerator.getDataUrl((dataUrl) => {
const targetElement = document.querySelector('#iframeContainer');
const iframe = document.createElement('iframe');
iframe.src = dataUrl;
targetElement.appendChild(iframe);
});
Parameters:
cb
- callback functionoptions
(optional)
const pdfDocGenerator = pdfMake.createPdf(docDefinition);
pdfDocGenerator.getBase64((data) => {
alert(data);
});
Parameters:
cb
- callback functionoptions
(optional)
const pdfDocGenerator = pdfMake.createPdf(docDefinition);
pdfDocGenerator.getBuffer((buffer) => {
// ...
});
Parameters:
cb
- callback functionoptions
(optional)
const pdfDocGenerator = pdfMake.createPdf(docDefinition);
pdfDocGenerator.getBlob((blob) => {
// ...
});
Parameters:
cb
- callback functionoptions
(optional)
var pdfMake = require('pdfmake/build/pdfmake.js');
var pdfFonts = require('pdfmake/build/vfs_fonts.js');
pdfMake.vfs = pdfFonts.pdfMake.vfs;
or
import pdfMake from "pdfmake/build/pdfmake";
import pdfFonts from "pdfmake/build/vfs_fonts";
pdfMake.vfs = pdfFonts.pdfMake.vfs;
For Ionic and Angular see issue.
If a Cannot read property 'TYPED_ARRAY_SUPPORT' of undefined error is thrown, add this to webpack config:
exclude: [ /node_modules/, /pdfmake.js$/ ]
(see issue)
see examples and dev-playground server script
pdfmake makes it possible to style any paragraph or its part:
var docDefinition = {
content: [
// if you don't need styles, you can use a simple string to define a paragraph
'This is a standard paragraph, using default style',
// using a { text: '...' } object lets you set styling properties
{ text: 'This paragraph will have a bigger font', fontSize: 15 },
// if you set the value of text to an array instead of a string, you'll be able
// to style any part individually
{
text: [
'This paragraph is defined as an array of elements to make it possible to ',
{ text: 'restyle part of it and make it bigger ', fontSize: 15 },
'than the rest.'
]
}
]
};
It's also possible to define a dictionary of reusable styles:
var docDefinition = {
content: [
{ text: 'This is a header', style: 'header' },
'No styling here, this is a standard paragraph',
{ text: 'Another text', style: 'anotherStyle' },
{ text: 'Multiple styles applied', style: [ 'header', 'anotherStyle' ] }
],
styles: {
header: {
fontSize: 22,
bold: true
},
anotherStyle: {
italics: true,
alignment: 'right'
}
}
};
To have a deeper understanding of styling in pdfmake, style inheritance and local-style-overrides check STYLES1, STYLES2 and STYLES3 examples in playground.
By default paragraphs are rendered as a vertical stack of elements (one below another). It is possible however to divide available space into columns.
var docDefinition = {
content: [
'This paragraph fills full width, as there are no columns. Next paragraph however consists of three columns',
{
columns: [
{
// auto-sized columns have their widths based on their content
width: 'auto',
text: 'First column'
},
{
// star-sized columns fill the remaining space
// if there's more than one star-column, available width is divided equally
width: '*',
text: 'Second column'
},
{
// fixed width
width: 100,
text: 'Third column'
},
{
// % width
width: '20%',
text: 'Fourth column'
}
],
// optional space between columns
columnGap: 10
},
'This paragraph goes below all columns and has full width'
]
};
Column content is not limited to a simple text. It can actually contain any valid pdfmake element. Make sure to look at the COLUMNS example in playground.
Conceptually tables are similar to columns. They can however have headers, borders and cells spanning over multiple columns/rows.
var docDefinition = {
content: [
{
layout: 'lightHorizontalLines', // optional
table: {
// headers are automatically repeated if the table spans over multiple pages
// you can declare how many rows should be treated as headers
headerRows: 1,
widths: [ '*', 'auto', 100, '*' ],
body: [
[ 'First', 'Second', 'Third', 'The last one' ],
[ 'Value 1', 'Value 2', 'Value 3', 'Value 4' ],
[ { text: 'Bold value', bold: true }, 'Val 2', 'Val 3', 'Val 4' ]
]
}
}
]
};
Own table layouts must be defined before calling pdfMake.createPdf(docDefinition)
.
pdfMake.tableLayouts = {
exampleLayout: {
hLineWidth: function (i, node) {
if (i === 0 || i === node.table.body.length) {
return 0;
}
return (i === node.table.headerRows) ? 2 : 1;
},
vLineWidth: function (i) {
return 0;
},
hLineColor: function (i) {
return i === 1 ? 'black' : '#aaa';
},
paddingLeft: function (i) {
return i === 0 ? 0 : 8;
},
paddingRight: function (i, node) {
return (i === node.table.widths.length - 1) ? 0 : 8;
}
}
};
// download the PDF
pdfMake.createPdf(docDefinition).download();
All concepts related to tables are covered by TABLES example in playground.
pdfMake supports both numbered and bulleted lists:
var docDefinition = {
content: [
'Bulleted list example:',
{
// to treat a paragraph as a bulleted list, set an array of items under the ul key
ul: [
'Item 1',
'Item 2',
'Item 3',
{ text: 'Item 4', bold: true },
]
},
'Numbered list example:',
{
// for numbered lists set the ol key
ol: [
'Item 1',
'Item 2',
'Item 3'
]
}
]
};
Page headers and footers in pdfmake can be: static or dynamic.
They use the same syntax:
var docDefinition = {
header: 'simple text',
footer: {
columns: [
'Left part',
{ text: 'Right part', alignment: 'right' }
]
},
content: (...)
};
For dynamically generated content (including page numbers, page count and page size) you can pass a function to the header or footer:
var docDefinition = {
footer: function(currentPage, pageCount) { return currentPage.toString() + ' of ' + pageCount; },
header: function(currentPage, pageCount, pageSize) {
// you can apply any logic and return any valid pdfmake element
return [
{ text: 'simple text', alignment: (currentPage % 2) ? 'left' : 'right' },
{ canvas: [ { type: 'rect', x: 170, y: 32, w: pageSize.width - 170, h: 40 } ] }
]
},
(...)
};
The background-layer will be added on every page.
var docDefinition = {
background: 'simple text',
content: (...)
};
It may contain any other object as well (images, tables, ...) or be dynamically generated:
var docDefinition = {
background: function(currentPage) {
return 'simple text on page ' + currentPage
},
content: (...)
};
Any element in pdfMake can have a margin:
(...)
// margin: [left, top, right, bottom]
{ text: 'sample', margin: [ 5, 2, 10, 20 ] },
// margin: [horizontal, vertical]
{ text: 'another text', margin: [5, 2] },
// margin: equalLeftTopRightBottom
{ text: 'last one', margin: 5 }
(...)
You could have figured out by now (from the examples), that if you set the content
key to an array, the document becomes a stack of paragraphs.
You'll quite often reuse this structure in a nested element, like in the following example:
var docDefinition = {
content: [
'paragraph 1',
'paragraph 2',
{
columns: [
'first column is a simple text',
[
// second column consists of paragraphs
'paragraph A',
'paragraph B',
'these paragraphs will be rendered one below another inside the column'
]
]
}
]
};
The problem with an array is that you cannot add styling properties to it (to change fontSize for example).
The good news is - array is just a shortcut in pdfMake for { stack: [] }, so if you want to restyle the whole stack, you can do it using the expanded definition:
var docDefinition = {
content: [
'paragraph 1',
'paragraph 2',
{
columns: [
'first column is a simple text',
{
stack: [
// second column consists of paragraphs
'paragraph A',
'paragraph B',
'these paragraphs will be rendered one below another inside the column'
],
fontSize: 15
}
]
}
]
};
This is simple. Just use the { image: '...' }
node type.
JPEG and PNG formats are supported.
var docDefinition = {
content: [
{
// you'll most often use dataURI images on the browser side
// if no width/height/fit is provided, the original size will be used
image: 'data:image/jpeg;base64,...encodedContent...'
},
{
// if you specify width, image will scale proportionally
image: 'data:image/jpeg;base64,...encodedContent...',
width: 150
},
{
// if you specify both width and height - image will be stretched
image: 'data:image/jpeg;base64,...encodedContent...',
width: 150,
height: 150
},
{
// you can also fit the image inside a rectangle
image: 'data:image/jpeg;base64,...encodedContent...',
fit: [100, 100]
},
{
// if you reuse the same image in multiple nodes,
// you should put it to to images dictionary and reference it by name
image: 'mySuperImage'
},
{
// under NodeJS (or in case you use virtual file system provided by pdfmake)
// you can also pass file names here
image: 'myImageDictionary/image1.jpg'
}
],
images: {
mySuperImage: 'data:image/jpeg;base64,...content...'
}
};
To add external or internal links, use the following syntax:
{text: 'google', link: 'http://google.com'}
{text:'Go to page 2', linkToPage: 2}
var docDefinition = {
content: [
{
toc: {
// id: 'mainToc' // optional
title: {text: 'INDEX', style: 'header'}
}
},
{
text: 'This is a header',
style: 'header',
tocItem: true, // or tocItem: 'mainToc' if is used id in toc
// or tocItem: ['mainToc', 'subToc'] for multiple tocs
}
]
}
var docDefinition = {
// a string or { width: number, height: number }
pageSize: 'A5',
// by default we use portrait, you can change it to landscape if you wish
pageOrientation: 'landscape',
// [left, top, right, bottom] or [horizontal, vertical] or just a number for equal margins
pageMargins: [ 40, 60, 40, 60 ],
};
If you set pageSize
to a string, you can use one of the following values:
- '4A0', '2A0', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'A10',
- 'B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9', 'B10',
- 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9', 'C10',
- 'RA0', 'RA1', 'RA2', 'RA3', 'RA4',
- 'SRA0', 'SRA1', 'SRA2', 'SRA3', 'SRA4',
- 'EXECUTIVE', 'FOLIO', 'LEGAL', 'LETTER', 'TABLOID'
To change page orientation within a document, add a page break with the new page orientation.
{
pageOrientation: 'portrait',
content: [
{text: 'Text on Portrait'},
{text: 'Text on Landscape', pageOrientation: 'landscape', pageBreak: 'before'},
{text: 'Text on Landscape 2', pageOrientation: 'portrait', pageBreak: 'after'},
{text: 'Text on Portrait 2'},
]
}
A pageBreakBefore
function can be specified, which can determine if a page break should be inserted before a node. To implement a 'no orphan child' rule, use a definition like this:
var dd = {
content: [
{text: '1 Headline', headlineLevel: 1},
'Some long text of variable length ...',
{text: '2 Headline', headlineLevel: 1},
'Some long text of variable length ...',
{text: '3 Headline', headlineLevel: 1},
'Some long text of variable length ...',
],
pageBreakBefore: function(currentNode, followingNodesOnPage, nodesOnNextPage, previousNodesOnPage) {
return currentNode.headlineLevel === 1 && followingNodesOnPage.length === 0;
}
}
If pageBreakBefore
returns true, a page break will be added before the currentNode
. Current node has the following information attached:
{
id: '<as specified in doc definition>',
headlineLevel: '<as specified in doc definition>',
text: '<as specified in doc definition>',
ul: '<as specified in doc definition>',
ol: '<as specified in doc definition>',
table: '<as specified in doc definition>',
image: '<as specified in doc definition>',
qr: '<as specified in doc definition>',
canvas: '<as specified in doc definition>',
columns: '<as specified in doc definition>',
style: '<as specified in doc definition>',
pageOrientation '<as specified in doc definition>',
pageNumbers: [2, 3], // The pages this element is visible on (e.g. multi-line text could be on more than one page)
pages: 6, // the total number of pages of this document
stack: false, // if this is an element which encapsulates multiple sub-objects
startPosition: {
pageNumber: 2, // the page this node starts on
pageOrientation: 'landscape', // the orientation of this page
left: 60, // the left position
right: 60, // the right position
verticalRatio: 0.2, // the ratio of space used vertically in this document (excluding margins)
horizontalRatio: 0.0 // the ratio of space used horizontally in this document (excluding margins)
}
}
PDF documents can have various metadata associated with them, such as the title, or author of the document. You can add that information by adding it to the document definition
var docDefinition = {
info: {
title: 'awesome Document',
author: 'john doe',
subject: 'subject of document',
keywords: 'keywords for document',
},
content: 'This is an sample PDF printed with pdfMake'
}
Standard properties:
- title - the title of the document
- author - the name of the author
- subject - the subject of the document
- keywords - keywords associated with the document
- creator - the creator of the document (default is 'pdfmake')
- producer - the producer of the document (default is 'pdfmake')
- creationDate - the date the document was created (added automatically by pdfmake)
- modDate - the date the document was last modified
- trapped - the trapped flag in a PDF document indicates whether the document has been "trapped"
Custom properties:
You can add custom properties. Key of property not contain spaces.
Compression of PDF is enabled by default, use compress: false
for disable:
var docDefinition = {
compress: false,
content: (...)
};
using npm:
git clone https://github.com/bpampuch/pdfmake.git
cd pdfmake
npm install
npm run build
using yarn:
git clone https://github.com/bpampuch/pdfmake.git
cd pdfmake
yarn
yarn run build
Hmmm... let me know what you need ;)
The goal is quite simple - make pdfmake useful for a looooooooot of people and help building responsive HTML5 apps with printing support.
There's one thing on the roadmap for v2 (no deadline however) - make the library hackable, so you can write plugins to:
- extend document-definition-model (with things like { chart: ... }),
- add syntax translators (like the provided [ ... ] -> { stack: [ ... ] }
- build custom DSLs on top of document-definition-model (this is actually possible at the moment).
MIT
pdfmake is based on a truly amazing library pdfkit (credits to @devongovett).
Thanks to all contributors.