# 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.2`).
- `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.

### 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.'
]);
...
```

## 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 builds on [pdf-lib] and
[fontkit]. 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
[fontkit]: https://github.com/Hopding/fontkit
