pdfmkr
Version:
Generate PDF documents from JavaScript objects
700 lines (557 loc) • 22.9 kB
Markdown
# PDF Maker
PDF Maker is a library for generating PDF documents in JavaScript.
## Usage
```sh
npm install pdfmkr
```
A `PdfMaker` instance creates PDF data from a given _document
definition_.
```ts
// Define the contents of the document
const doc = {
content: [text('Hello World!')],
};
// create an instance of PdfMaker and register fonts
const pdfMaker = new PdfMaker();
pdfMaker.registerFont(await readFile('path/to/Roboto-Regular.ttf'));
// create a PDF from the document
const pdfData = await pdfMaker.makePdf(doc);
await writeFile(`hello.pdf`, pdfData);
```
## Fonts
Fonts need to be registered before they can be used in a document. The
`registerFont()` method accepts font data in `TTF` or `OTF` format.
Fonts will be selected based on their font family, font style, and font
weight. This information is extracted from the font by `registerFont()`,
it but can also be provided as a second parameter if needed.
```ts
pdfMaker.registerFont(await readFile('path/to/Roboto-Regular.ttf'));
pdfMaker.registerFont(await readFile('path/to/Roboto-Italic.ttf'));
...
const doc = {
// Define the default font
defaultStyle: { fontFamily: 'Roboto', fontSize: 12 },
content: [
// This block will use the Roboto-Regular font
text('Hello World!')
// This block will use the Roboto-Italic font
text('Hello World!', { fontStyle: 'italic' })
],
};
```
If no font family is specified in the document, the first
matching font will be used.
## Content
The content of a document is composed of _blocks_. There are different
types of blocks, such as text blocks, image blocks, column and row
layout blocks. The `content` property of the document definition accepts
a list of blocks.
### Text
Text blocks can be created using the `text()` function. Besides the
text to display, this function accepts [block](#block-properties) and
[text](#text-properties) properties.
```ts
const block = text('Lorem ipsum', { fontStyle: 'italic', fontSize: 12 });
```
#### Text spans
The text property can be a single string or an array of strings or text
spans. A text span is an inline stretch of text with a specific style,
such as an emphasized word or a link. Text spans can be created using
the `span()` function. Text spans support [text
properties](#text-properties).
```ts
const block = text([
'This is some text with an ',
span('emphasized text span', { fontStyle: 'italic' }),
' in the middle.',
]);
```
This block will result in the following text:
> This is some text with an _emphasized text span_ in the middle.
Text spans can also be nested:
```ts
span(['This is ', span('super', { fontWeight: 'bold' }), ' important'], { fontStyle: 'italic' });
```
> _This is **super** important._
For convenience, the functions `bold()` and `italic()` can be used to
create bold and italic text spans:
```ts
italic(['This is ', bold('super'), ' important']);
```
#### Line breaks
Line breaks are inserted automatically at word boundaries. Explicit
line breaks can be inserted as line feed characters (`\n`):
```ts
text('Explicit line\nbreaks are\nsupported.');
```
#### Subscripts and superscripts
Subscripts and superscripts can be created using the `span()` function
with the `rise` property. This property does not affect the line height.
```ts
text(['H', span('₂', { rise: -3 }), 'O']);
text(['10', span('⁻³', { rise: 3 }), '.']);
```
#### Links
Text spans can also be used to create links to external URLs or to
[anchors](#anchors) in the document.
```ts
span('example.com', { link: 'https://example.com', color: 'blue' });
```
#### Text alignment
Text alignment can be set using the `textAlign` property. The value can
be `left`, `center`, or `right`. The default is `left`.
```ts
text('Centered text', { textAlign: 'center' });
```
The text alignment can be defined for an entire block and is propagated
to all children in the block. Children can override the alignment.
```ts
rows(
[
text("I'm centered."),
text("I'm centered."),
text("I'm not!", { textAlign: 'left' }),
],
{ textAlign: 'center' },
),
```
#### Text properties
Text properties can be set for an entire text block or for individual
text spans. The following text properties are supported:
- `fontFamily`: The font family to use.
- `fontStyle`: The font style. Can be `normal`, `italic`, or `bold`.
- `fontWeight`: The font weight. Can be a number between `100` and
`900`. The literal values `normal` and `bold` are also supported.
- `fontSize`: The font size in pt.
- `lineHeight`: The line height as a multiple of the font size (default:
`1`).
- `color`: The text [color](#colors).
- `link`: Renders the text as a link to the given target. Can be a URL
or an [anchor](#anchors) reference.
- `rise`: Vertical offset in pt for baseline shifts. Positive values
shift the baseline up, negative values shift it down.
- `letterSpacing`: The character spacing in pt. Positive values increase
the space between characters, negative values decrease it.
- `fontKerning`: Controls kerning (pair-wise spacing adjustments). Can
be `normal` (default) or `none`.
- `fontVariantLigatures`: Controls ligatures and contextual alternates.
Can be `normal` (default) or `none`.
- `fontFeatureSettings`: Low-level control over OpenType font features.
An object where each key is a four-character OpenType feature tag and
the value enables (`true`) or disables (`false`) that feature
(e.g. `{ smcp: true, tnum: true }`).
- `language`: The language of the text as a BCP 47 tag (e.g. `'en'`,
`'tr'`, `'sr'`). The language may influence text shaping in fonts
that provide language-specific typographic behavior. If not set, the
document's default language is used.
### Images
Images can be included in image blocks, which can be created using the
`image()` function. This function accepts the image URL and an optional
object containing [image](#image-properties) and
[block](#block-properties) properties. Images are supported in JPG and
PNG format. URLs can be `data:`, `http:`, `https:`, or `file:` URLs. The
size of an image can be confined using the `width` and `height`
properties.
```ts
const block = image('file:/images/logo.png', { width: 200, height: 100 });
```
When the same image URL is used multiple times in the document, the
image data is embedded in the PDF only once.
#### Image properties
- `imageAlign`: Aligns the image within the block. The alignment of the
image within the block. Supported values are `left`, `center`, and
`right`. The default is `center`.
### Graphics
Each block can have a `graphics` property that accepts a list of
_shapes_ to draw into that block. Alternatively, this property accepts a
function that returns a list of shapes. The function will be called with
the block's width and height. This can be used to draw shapes that
depend on the block's size. The coordinate system for graphics shapes
starts at the top left corner of the block.
Shapes can be lines, rectangles, circles, or SVG paths. They can be
created using the `line()`, `rect()`, `circle()`, and `path()`
functions.
In the following example, the `graphics` property is used to draw a
yellow background behind the text and a blue border at the left edge.
```ts
text('Lorem ipsum', {
graphics: ({ width, height }) => [
rect(0, 0, width, height, { fillColor: 'yellow' }),
line(0, 0, 0, height, { lineColor: 'blue', lineWidth: 2 }),
],
padding: { left: 5 },
});
```
#### Lines
Lines are defined by the coordinates of the start and end points of the
line. They support [stroke](#stroke-properties) and
[transform](#transform-properties) properties.
```ts
line(10, 20, 90, 20, { lineColor: 'blue', lineWidth: 2 });
```
#### Rectangles
Rectangles are defined by the coordinates of the top-left corner, the
width, and height of the rectangle. They support
[stroke](#stroke-properties), [fill](#fill-properties), and
[transform](#transform-properties) properties.
```ts
rect(10, 20, 50, 25, { lineColor: '#4488cc', lineJoin: 'round' });
```
#### Circles
Circles are defined by the coordinates of the center and the radius of
the circle. They support [stroke](#stroke-properties),
[fill](#fill-properties), and [transform](#transform-properties)
properties.
```ts
circle(cx, cy, 20, { fillColor: 'red' });
```
#### Paths
Paths allow to create arbitrary shapes using a series of drawing
commands. These commands are accepted in the format of an SVG path data
string (see
[MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d) for
details). Paths support [stroke](#stroke-properties),
[fill](#fill-properties), and [transform](#transform-properties)
properties.
```ts
path('M45,0 L15,94 L90,34 L0,34 L75,94 Z', {
fillColor: '#4488cc',
translate: { x: 100 },
});
```
#### Stroke properties
Stroke properties define the appearance of stroked lines. The following
stroke properties are supported:
- `lineWidth`: The width of stroked lines in pt.
- `lineColor`: The [color](#colors) of stroked lines.
- `lineOpacity`: The opacity of stroked lines as a number between `0`
and `1`.
- `lineCap`: The shape at the end of open paths when they are
stroked.
- `butt`: indicates that the stroke for each subpath does not extend
beyond its two endpoints. On a zero length subpath, the path will
not be rendered at all.
- `round`: indicates that at the end of each subpath the stroke will
be extended by a half circle with a diameter equal to the stroke
width. On a zero length subpath, the stroke consists of a full
circle centered at the subpath's point.
- `square`: indicates that at the end of each subpath the stroke will
be extended by a rectangle with a width equal to half the width of
the stroke and a height equal to the width of the stroke. On a zero
length subpath, the stroke consists of a square with its width equal
to the stroke width, centered at the subpath's point.
- `lineJoin`: The shape to be used at the corners of paths or
basic shapes when they are stroked.
- `miter`: indicates that the outer edges of the strokes for the two
segments should be extended until they meet at an angle, as in a
picture frame.
- `round`: indicates that the outer edges of the strokes for the two
segments should be rounded off by a circular arc with a radius equal
to half the line width.
- `bevel`: indicates that the two segments should be finished with
butt caps and the resulting notch should be filled with a triangle.
- `lineDash` (`number[]`): The dash pattern to use for drawing paths. Each
element defines the length of a dash or a gap, in pt, starting with
the first dash. If the array contains an odd number of elements, then
the elements are repeated to yield an even number of elements. An
empty array stands for no dash pattern, i.e. a continuous line.
#### Fill properties
Fill properties define the appearance of the areas enclosed by paths or
basic shapes. The following fill properties are supported:
- `fillColor`: The [color](#colors) to use for filling the shape.
- `fillOpacity`: The opacity to use for filling the shape as number
between `0` and `1`.
#### Transform properties
Transform properties can be used to move, resize, rotate, or transform
shapes in other ways. The following transform properties are supported:
- `translate` (`{ x: number, y: number }`): Moves the shape by `x` and
`y` pt.
- `scale` (`{ x: number, y: number }`): Stretches the shape by `x` and
`y` pt.
- `rotate` (`{ angle: number, cx?: number, cy?: number }`): Rotates the
shape by `angle` degrees clockwise about the point `[cx,cy]`. If `cx`
and `cy` are omitted, the rotation is about the origin of the
coordinate system.
- `skew` (`{ x: number, y: number }`): Skews the shape by `x` degrees
along the x axis and by `y` degrees along the y axis.
- `matrix` (`number[]`): Applies a custom transformation matrix to the
shape. The matrix is given as an array of six values `[a, b, c, d, e,
f]` that represent the transformation matrix:
```
| a c e |
| b d f |
| 0 0 1 |
```
### Colors
Colors can be specified in the format `#rrggbb` where `rr`, `gg`, and
`bb` are the red, green, and blue components respectively, in
hexadecimal notation.
In addition, the named colors `black`, `gray`, `white`, `red`, `blue`,
`green`, `cyan`, `magenta`, `yellow`, `lightgray`, `darkgray` are
supported.
## Layout
### Columns
To arrange blocks horizontally, they can be included in a _columns_
block. The width of each column can be set using the `width` property.
When the width is set to `auto`, the column will shrink to the width of
its content. The remaining space is distributed evenly across all
columns that don't have a fixed width.
Column blocks can be created using the `columns()` function, which takes
an array of blocks and an optional object with
[block](#block-properties) and [text](#text-properties) properties. Text
properties will be inherited by included text blocks.
```ts
const block = columns([
text('Column 1', { width: 100 }), // 100 pt wide
text('Column 2'), // gets half of the remaining width
text('Column 3'), // gets half of the remaining width
]);
```
### Rows
To arrange blocks vertically, they can be included in a _rows_ block.
This can be useful to group multiple rows into a single block, e.g. to
apply common properties or to enclose rows in a surrounding columns
layout.
Row blocks can be created using the `rows()` function, which takes an
array of blocks and an optional object with
[block](#block-properties) and [text](#text-properties) properties. Text
properties will be inherited by included text blocks.
```ts
const block = rows(
[text('Row 1'), text('Row 2'), text('Row 3')],
{ margin: 10, fontSize: 18 }, // fontSize is applied to all rows
);
```
### Margin
The `margin` property can be used to add space around blocks. It
accepts either a single value (applies to all four edges) an object with
any of the properties `top`, `right`, `bottom`, `left`, `x`, and `y`.
The properties `x` and `y` can be used as shorthands to set both `left`
and `right`, or `top` and `bottom`, respectively. Values can be given
as numbers (in pt) or as strings with a unit. If a string is given, it
must contain one of the units `pt`, `in`, `mm`, or `cm`;
```ts
text('Lorem ipsum', { margin: 5 }); // 5 pt margin on all sides
```
```ts
text('Lorem ipsum', { margin: { y: '5cm' } }); // 5 cm top and bottom margin
```
The `top` and `bottom` margins of adjacent blocks
are collapsed into a single margin whose size is the maximum of the two
margins. Column margins don't collapse.
```ts
rows([
text('Lorem ipsum', { margin: { y: 5 } }),
// only 5 pt margin between the two blocks
text('dolor sit amet', { margin: { y: 5 } }),
]);
```
### Padding
The `padding` property can be used to add space between the content and
the edges of blocks. It accepts the same values as the `margin`
property.
```ts
text('Lorem ipsum', { padding: { x: '5pt', y: '2pt' });
```
#### Block properties
The following properties can be set for all types of blocks:
- `padding`: Space to leave between the content and the edges of the
block.
- `margin`: Space to surround the block.
- `width`: The width of the block. If this property is set to `auto`,
the block will use the width of the widest element in the block.
- `height`: The height of the block. If this property is not set, the
height of the block is defined by its content.
- `verticalAlign`: Aligns the block vertically within a columns block.
Supported values are `top`, `middle`, and `bottom`. By default, blocks
are top-aligned.
- `id`: An optional _unique_ id for the element that can be used as an
[anchor](#anchors).
- `graphics`: A list of [graphic](#graphics) elements to draw in the
area covered by the block. A function can be passed to take the final
size of the block into account.
- `breakBefore`: Controls whether a page break may occur before the
block.
- `auto` (default): Insert a page break when needed.
- `always`: Always insert a page break before this block.
- `avoid`: Do not insert a page break before this block if it can be
avoided.
- `breakAfter`: Controls whether a page break may occur after the block.
- `auto` (default): Insert a page break when needed.
- `always`: Always insert a page break after this block.
- `avoid`: Do not insert a page break after this block if it can be avoided.
## Page layout
### Page size
The top-level `pageSize` property can be used to set the page size.
Various standard sizes are supported, such as `A4`, `Letter`, and
`Legal`. The default is A4. A custom page size can be specified as an
object with the properties `width` and `height`. Values can be given as
numbers (in pt) or as strings with a unit.
```ts
const document = {
pageSize: { width: '20cm', height: '20cm' }
content: [text('Lorem ipsum')],
};
```
### Page orientation
The `pageOrientation` property can be used to set the page orientation.
The value can be either `portrait` or `landscape`. The default is
portrait.
```ts
const document = {
pageSize: 'A5',
pageOrientation: 'landscape',
content: [text('Lorem ipsum')],
};
```
### Headers and footers
Headers and footers that repeat on each page can be defined using the
optional `header` and `footer` properties. Both accept either a single
block or a function that returns a block. The function will be called
with the page number and the total number of pages. The page number
starts at 1.
```ts
const document = {
footer: ({ pageNumber, pageCount }) =>
text(`Page ${pageNumber} of ${pageCount}`, {
textAlign: 'right',
margin: { x: '20mm', bottom: '7mm' },
}),
content: [text('Lorem ipsum'), text('dolor sit amet')],
};
```
### Page breaks
Page breaks are included automatically. When a block does not fit on the
current page, a new page is added to the document. To insert a page
break before or after a block, set the `breakBefore` or `breakAfter`
property of a block to `always`. To prevent a page break, set this
property to `avoid`.
Page breaks are also automatically inserted between the lines of a text
block. To prevent a page break within a text block, set the
`breakInside` property to `avoid`.
```ts
const document = {
content: [
text('Lorem ipsum'),
text('This text will go on a new page', { breakBefore: 'always' }),
],
};
```
## Anchors
Anchors can be used to create links to other locations in the document.
An anchor is created by setting a unique `id` property to the block that
should be the target of the link:
```ts
text('Section Two', { id: 'section2' });
```
An internal reference to this anchor can be created by setting the
`link` property of a text span to a hash sign (`#`), followed by the
`id` of the target block:
```ts
text([
'See ',
span('Section Two', { link: '#section2' }), // Link to a section
' for more information.'
]);
...
```
## Language
The `language` property sets the default language of the document as a
BCP 47 tag (e.g. `'en'`, `'de'`, `'ar'`). It is written to the PDF
catalog for accessibility support.
The language can also be set on individual text blocks or spans using
the `language` [text property](#text-properties). Fonts that provide
language-specific typographic behavior (e.g. for Turkish or Serbian)
will use the correct glyph forms based on the language of the text.
## Metadata
PDF documents can include metadata such as the title, author, subject,
and keywords. This information can be set using the `info` property of
the document.
```ts
const document = {
info: {
title: 'Invoice Dec 2024',
author: 'John Doe',
},
content: [text('Hello World!')],
};
```
The following properties are supported:
- `title`: The document’s title.
- `author`: The name of the person who created the document.
- `subject`: The subject of the document.
- `keywords`: Keywords associated with the document.
- `creationDate`: The date and time the document was created. If not
set, the current time is used.
- `modificationDate`: The date and time the document was last modified.
If not set, the current time is used.
- `creator`: The name of the application that created the original
content.
- `producer`: The name of the application that converted the original
content into a PDF.
## Embedded files
Supplementary files can be stored directly within a PDF document. This
can be useful for creating self-contained documents, such as for
archival purposes. Those files can be added to the document using the
`embeddedFiles` property, which accepts an array of objects, each
representing a file with the following properties:
- `content`: The binary content of the file as a `Uint8Array`.
- `fileName`: The name of the file as it will appear in the list of
attachments in the PDF viewer.
- `mimeType`: The MIME type of the file.
- `description` (optional): A brief description of the file's content or
purpose. This information can be displayed to the user in the PDF
viewer.
- `creationDate` (optional): The date and time when the file was
created.
- `modificationDate` (optional): The date and time when the file was
last modified.
- `relationship` (optional): A name that specifies the relationship
between the file and the document.
```ts
const document = {
content: [text('Hello World!')],
embeddedFiles: [
{
fileName: "Study-Results-2025.csv",
mimeType: "text/csv",
content: /* binary data of the data */,
mimeType: 'image/png',
description: "CSV file containing the result data of the 2025 study.",
creationDate: new Date("2025-01-12"),
},
],
};
```
## Dev tools
### Visual Debugging
This feature can be used to visually inspect the structure and layout of
blocks in the PDF document during development. When enabled, it includes
some visual guides in the generated PDF:
- Each block is rendered with a gray border to indicate its bounds.
- Page headers and footers are separated from the page content by a
horizontal line.
- Each line of text is surrounded by a thin green border. Another thin
green line indicates the text baseline.
- Margins and paddings get a semi-transparent overlay. Margins are shown
in yellow, paddings in purple. Overlapping margins will
result in a darker shade of yellow.
To enable visual debugging, set the `dev.guides` property in the
document to `true`:
```ts
const document = {
dev: { guides: true }, // Enable visual debugging
content: [
text('Hello World!'),
],
...
};
```
## License
[MIT](LICENSE.txt)
## Thanks
This project is inspired by [pdfmake] and [pdf-lib]. It would not exist
without the great work and the profound knowledge contributed by the
authors of those projects.
[pdfmake]: https://github.com/bpampuch/pdfmake
[pdf-lib]: https://github.com/Hopding/pdf-lib