Skip to content

Commit

Permalink
📝 Revise and complete README
Browse files Browse the repository at this point in the history
This commit adds sections on graphics, margins and paddings, page size
and orientation, headers and footers, and page breaks to the README.
  • Loading branch information
ralfstx committed Oct 3, 2023
1 parent da09e7b commit f05eed6
Showing 1 changed file with 175 additions and 47 deletions.
222 changes: 175 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,40 @@

PDF Maker is a library for generating PDF documents in JavaScript.

- Easy to use: document contents are defined in plain objects.
- Works anywhere: Browser, Node.js, Deno.
- TypeScript support: types included in the npm package.
- Easy to use: contents are defined in plain objects.
- Works anywhere: in the browser, in Node.js, Deno, and Bun.
- TypeScript support: types are included in the npm package.

This project is heavily 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
these projects.
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.

## Usage

The function `makePdf()` creates PDF data from a given [document definition](src/content.ts).
The `makePdf()` function creates PDF data from a given _document
definition_. This definition is a plain object.

### Content

The most important attribute in the definition is named `content`. This
attribute accepts a list of _blocks_. There are different types of
blocks, such as text blocks, image blocks, column and row layout blocks.

### Basic Example

```js
const fontData = await readFile('Roboto-Regular.ttf');
const fontDataBold = await readFile('Roboto-Medium.ttf');
const fontDataItalic = await readFile('Roboto-Italic.ttf');

const pdfData = await makePdf({
// Fonts must be registered (see below)
fonts: {
Roboto: [{ data: fontData }, { data: fontDataBold, bold: true }],
Roboto: [{ data: fontData }, { data: fontDataItalic, italic: true }],
},
// Page margins (`x` is a shorthand for left and right)
margin: { x: '2.5cm', top: '2cm', bottom: '1.5cm' },
// Content as an array of blocks
content: [
// Blocks can contain text and text attributes
{ text: 'Lorem ipsum', bold: true, textAlign: 'center', fontSize: 24 },
{ text: 'Lorem ipsum', italic: true, textAlign: 'center', fontSize: 24 },
// Text can also be an array of text ranges with different attributes
{
text: [
Expand All @@ -43,26 +49,32 @@ const pdfData = await makePdf({
await writeFile(`hello.pdf`, pdfData);
```

There are more examples in the [examples/](examples/) folder.

### Fonts

All fonts are embedded in the PDF and need to be registered with the `fonts` attribute.
Font data is accepted in `.ttf` or `.otf` format, as ArrayBuffer, Uint8Array, or base64-encoded
string.
Each font family can include different variants that are selected based on the attributes `bold` and
`italic`.
All fonts are embedded in the PDF and must be registered with the
`fonts` attribute. Font data is accepted in `.ttf` or `.otf` format, as
ArrayBuffer or Uint8Array. Each font family can contain different
variants which are selected based on the attributes `bold` and `italic`.
The font family that is registered first becomes the default.

```js
const documentDefinition = {
fonts: {
// The `fontFamily` name of the font
'DejaVu-Sans': [
// TTF / OTF font data as Uin8Array or base64 encoded string
// Different font versions for fontFamily "DejaVu-Sans"
// TTF / OTF font data as ArrayBuffer or Uin8Array
{ data: fontDataDejaVuSansNormal },
{ data: fontDataDejaVuSansBold, bold: true },
{ data: fontDataDejaVuSansItalic, italic: true },
{ data: fontDataDejaVuSansBoldItalic, bold: true, italic: true },
],
Roboto: [{ data: fontDataRobotoNormal }, { data: fontDataRobotoMedium, bold: true }],
Roboto: [
// Different font versions for fontFamily "Roboto"
{ data: fontDataRobotoNormal },
{ data: fontDataRobotoMedium, bold: true },
],
},
content: [
{ text: 'lorem ipsum', fontFamily: 'Roboto', bold: true }, // will use Roboto Medium
Expand All @@ -73,10 +85,10 @@ const documentDefinition = {

### Images

JPG images are supported. All images need to be registered with the `images` attribute.
Images can be used more than once in the document without multiplying the image's footprint in the
created PDF.
The size of an image can be confined using the `width` and `height` attributes.
JPG and PNG images are supported. All images must be registered with the
`images` attribute. Images can be used multiple times in the document,
but the image data is only included once in the PDF. The size of an
image can be confined using the `width` and `height` attributes.

```js
const documentDefinition = {
Expand All @@ -85,60 +97,176 @@ const documentDefinition = {
},
content: [
// An image block
{ image: 'logo', width: 200, height: 100 },
]
};
```

### Content

The `content` attribute of the document definition accepts an array of top-level _blocks_ that can
be text blocks, image blocks, column or row layout blocks.
Page breaks will only occur between top-level blocks.

### Columns

To arrange blocks horizontally, they can be included in a block with a `columns` attribute.
When columns have a `width` attribute, it will be respected.
The remaining space will be distributed evenly across all columns.
To arrange blocks horizontally, they can be included in a block with a
`columns` attribute. When columns have a `width` attribute, it is
respected. The remaining space id distributed evenly across all columns.

```js
{
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
{ 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

A rows layout can be used to group multiple rows in a single block, e.g. to apply common attributes
to them or to include them in a surrounding columns layout.
A row layout can be used to group multiple rows into a single block,
e.g. to apply common attributes or to enclose rows in a surrounding
columns layout.

```js
{
rows: [
{text: 'Row 1'},
{text: 'Row 2'},
{text: 'Row 3'},
{ text: 'Row 1' },
{ text: 'Row 2' },
{ text: 'Row 3' },
],
textAlign: 'right',
}
```

## Documentation
### Graphics

Each block can have a `graphics` attribute that accepts a list of shapes
to draw into that block or 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.

Shapes can be lines, rectangles, circles, or SVG paths. In the following
example, a graphics attribute is used to draw a yellow background behind
the text and a blue border at the left edge.

```js
{
text: 'Lorem ipsum',
graphics: ({ width, height }) => [
{ type: 'rect', x: 0, y: 0, width, height, fillColor: 'yellow' },
{ type: 'line', x1: 0, y1: 0, x2: 0, y2: height, lineColor: 'blue', lineWidth: 2 },
],
padding: { left: 5 },
}
```

Also see the [graphics example](examples/src/graphics.js).

### Margin and padding

The `margin` attribute 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 attributes `top`, `right`, `bottom`, `left`, `x`, and `y`.
The attributes `x` and `y` can be used as shorthands to set both `left`
and `right` or `top` and `bottom` at the same time. 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`;

```js
{
margin: { x: 5, top: 10 },
content: [
{ text: 'Lorem ipsum' },
{ text: 'dolor sit amet' },
],
}
```

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.

The `padding` attribute can be used to add space between the content and
the edges of blocks.

### Page layout

The top-level `pageSize` attribute 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 attributes `width` and `height`. Values can be given as
numbers (in pt) or as strings with a unit.

There is no generated documentation yet, please refer to [content.ts](src/content.ts) for an
overview and specification of all supported attributes in a document definition.
```js
{
pageSize: { width: '20cm', height: '20cm' }
}
```

The `pageOrientation` attribute can be used to set the page orientation.
The value can be either `portrait` or `landscape`. The default is
portrait.

```js
{
pageSize: 'A5',
pageOrientation: 'landscape',
content: [
{ text: 'Lorem ipsum' },
{ text: 'dolor sit amet' },
],
}
```

### Headers and footers

Headers and footers that repeat on each page can be defined using the
optional `header` and `footer` attributes. 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.

```js
{
footer: ({ pageNumber, pageCount }) => ({
text: `Page ${pageNumber} of ${pageCount}`,
textAlign: 'right',
margin: { x: '20mm', bottom: '1cm' },
}),
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`
attribute of a block to `always`. To prevent a page break, set this
attribute 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` attribute to `avoid`.

## Example
```js
{
content: [
{ text: 'Lorem ipsum' },
{ text: 'This text will go on a new page', breakBefore: 'always' },
],
}
```

## Documentation

A more extensive example is included in the [examples/](examples/) folder.
While there is no generated documentation yet, you can refer to the
[content.ts](src/content.ts) for a specification of all supported
attributes in a document definition.

Run `npm start` to generate a PDF in the `out/` folder on every change of this file.
Also check out the examples in the [examples/](examples/) folder.

## License

Expand Down

0 comments on commit f05eed6

Please sign in to comment.