npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@replytechnologies/reply-pdf

v0.0.8

Published

templated pdf generation

Downloads

16

Readme

ReplyPDF

BADGE_NPM_DOWNLOADS BADGE_NPM_DOWNLOADS BADGE_NPM_VERSION BADGE_NPM_LICENCE BADGE_PAYPAL

Table of Contents

[TOC]

About

If you ever had to create a PDF document you would be familiar with the struggles to get headers, footers, and tables to layout correctly. This library aims to remove the complexity of creating PDF documents by providing purpose built components that handle rendering and data binding.

Interactive demo coming soon

Installation

Node

npm install @replytechnologies/reply-pdf

Usage

Import

CommonJS: Node.js <= 12

const ReplyPDF = require('@replytechnologies/reply-pdf');
const { ...components... } = ReplyPDF.components;
const { ...enums... } = ReplyPDF.enums;
const { ...models... } = ReplyPDF.models;

CommonJS: Node.js > 12

const ReplyPDF = require('@replytechnologies/reply-pdf');
const { ...components... } = require('@replytechnologies/reply-pdf/components');
const { ...enums... } = require('@replytechnologies/reply-pdf/enums');
const { ...models... } = require('@replytechnologies/reply-pdf/models');

EJS

import ReplyPDF from '@replytechnologies/reply-pdf';
import { ...components... } from '@replytechnologies/reply-pdf/components';
import { ...enums... } from '@replytechnologies/reply-pdf/enums';
import { ...models... } from '@replytechnologies/reply-pdf/models';

PDF Generation

The basic steps that should be taken to generate a PDF are outlined in the example below:

// create document template
const template = new Page({ ...options... });

// create binding data
const data = { ..binding data... };

// generate PDF
let doc = ReplyPDF.generateDocument({
    data: data,
    template: template
});

// handle document object (review PDFKit documentation for alternatives)
doc.pipe(fs.createWriteStream(outputFilePath));

// close document object
doc.close();

PDF usage

The output object returned from ReplyPDF.generateDocument({...}) is of type PDFDocument. For different methods of handling the returned object, refer to the PDFKit documentation found here.

Under the Hood

PDFKit: renders native elements to the PDF document.

SVG-To-PDFKit: renders SVG images to the PDF document.

Components

component_hierarchy

BaseComponent

BaseComponent registers properties and functions shared by all components

Hierarchy: BaseComponent

| Property | Type | Description | | --------- | ------ | ------------------------------------------------------------ | | binding | string | Property of the provided data object to use as data source for bindings | | debug | bool | Indicates whether the component should render rectangles for outline and margin |

BaseLayoutComponent

BaseLayoutComponent registers properties and functions shared by all components that have a physical layout on the document.

Hierarchy: BaseComponent / BaseLayoutComponent

| Property | Type | Description | | ------------------- | --------------- | -------------------------------------------- | | width | number | Width available to render content | | height | number | Height available to render content | | x | number | Horizontal content offset | | y | number | Vertical content offset | | margin | Offset | Content inset from provided width and height | | border | Border | Border to draw around component | | verticalAlignment | Alignment | Vertical alignment within parent component | | horizontalAlignment | Alignment | Horizontal alignment within parent component | | backgroundColor | string | Color of background fill | | link | string | Link to navigate to on click |

BaseContainerComponent

BaseContainerComponent registers properties and functions shared by all components that contain explicit child elements.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseContainerComponent

| Property | Type | Description | | -------- | ----------- | -------------------------------------- | | children | [BaseComponent] | Array of children to render as content |

BaseStackComponent

BaseStackComponent registers properties and functions shared by all components that perform stacking of child components.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseContainerComponent / BaseStackComponent

| Property | Type | Description | | -------- | ----------- | -------------------------------------- | | spacing | number | Spacing between child components | | layout | Layout | Layout mode of child components |

BaseTextComponent

BaseTextComponent registers properties and functions shared by all text oriented components.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseTextComponent

| Property | Type | Description | | ------------- | ----------------- | ------------------------------------------------------------ | | text | string | Text to render as content | | textAlignment | TextAlignment | Text alignment of content | | fontSize | number | Font size of rendered text | | fontFamily | string | Font family of rendered text | | fontWeight | FontWeight | Font weight of rendered text | | lineBreak | bool | Indicates whether text is allowed to span multiple lines | | ellipsis | string | Indicates the character(s) to append to the end of a string when lineBreak is set to false and the text tries to wrap into the next line |

Page

Page should always be the base component of a template. All child components of a page are provided with the entire page content size to render within.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseContainerComponent / Page

| Property | Type | Description | | ----------------- | ----------------- | ----------------------------------------------------- | | header | BaseComponent | Component template to render as header | | footer | BaseComponent | Component template to render as footer | | firstPageHeader | BaseComponent | Component template to render as header for first page | | size | Size | Defines the page size |

Example

Go to usage example | Go to sample PDF

const template = new Page({
    size: PageSize.A4,
    margin: new Offset(25),
    header: new Container({
        height: 50,
        backgroundColor: 'sky',
        border: new Border(),
        children: [
            ...
        ],
    }),
    footer: new Container({
        height: 50,
        children: [
            ...
        ],
    }),
    children: [
        ...
    ],
});

component_example_page

Container

Container provides functionality to render child components according to the values of their respective verticalAlignment and horizontalAlignment values.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseContainerComponent / Component

Example

Go to usage example | Go to sample PDF

const container = new Container({
    children: [
        new Text({
            text: 'Lorem ipsum ... anim id est laborum.',
            y: 50,
            width: 400,
            textAlignment: TextAlignment.center,
            horizontalAlignment: Alignment.middle,
            verticalAlignment: Alignment.start,
        }),
        new Text({
            text: 'top left',
            horizontalAlignment: Alignment.start,
            verticalAlignment: Alignment.start,
        }),
        new Text({
            text: 'top center',
            horizontalAlignment: Alignment.middle,
            verticalAlignment: Alignment.start,
        }),
        new Text({
            text: 'top right',
            horizontalAlignment: Alignment.end,
            verticalAlignment: Alignment.start,
        }),
    ],
}),

component_example_container

StackHorizontal

StackHorizontal provides functionality to layout its children horizontally. StackHorizontal is sized automatically according to the size of its children. StackHorizontal does not overflow children into a new row, and does not request a new page when content reaches the end of the page.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseContainerComponent / BaseStackComponent / StackHorizontal

Example

Go to usage example | Go to sample PDF

new StackHorizontal({
    children: [
        new Text({ text: '1', width: 50 }),
        new Text({ text: '2' }),
        new Text({
            backgroundColor: 'orange',
            text: '3',
            margin: new Offset(15)
        }),
        new Text({ text: '4' }),
        new Text({ text: '5' }),
    ],
}),

component_example_stack_horizontal

StackVertical

StackVertical provides functionality to layout its children vertically. StackVertival is sized automatically according to the size of its children. StackVertical does not overflow children into a new column, and does not request a new page when content reaches the end of the page.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseContainerComponent / BaseStackComponent / StackVertical

Example

Go to usage example | Go to sample PDF

new StackVertical({
    children: [
        new Text({ text: '1', height: 50 }),
        new Text({ text: '2' }),
        new Text({
            backgroundColor: 'pink',
            text: '3',
            margin: new Offset(15)
        }),
        new Text({ text: '4' }),
        new Text({ text: '5' }),
    ],
}),

component_example_stack_vertical

GridLayout

GridLayout provides functionality to layout components horizontally in a specified number of columns. When the number of columns is reached, components are added to a new row. When content reaches the end of the page, it will continue to the next page.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseContainerComponent / GridLayout

| Property | Type | Description | | --------- | ------ | --------------------------------------------- | | columns | number | Number of columns in which to display content |

Example

Go to usage example | Go to sample PDF

const children = [];
for (var i = 0; i < 5; i++) {
    children.push(
        new Text({
            margin: new Offset(10),
            text: `Item: ${i}`,
            fontSize: 20
        }),
    );
}
...
new GridLayout({
    columns: 3,
    children: children
})

component_example_grid_layout

Text

Text provides functionality to render text to the document.

Hierarchy: BaseComponent / BaseLayoutComponent / BaseTextComponent / Text

| Property | Type | Description | | --------------- | ------------- | ------------------------------------------------------------ | | linkStyle | object | Style to apply to text if value of link is present | | color | string | Text color | | underline | bool | Indicates whether the text should be underlined | | strikethrough | bool | Indicates whether the text should be striked | | italic | bool / number | Indicates whether text should be rendered at an angle, uses a specific angle if italic is set to a number |

Example

Go to usage example | Go to sample PDF

 new Text({
     margin: new Offset(10),
     fontWeight: FontWeight.bold,
     text: 'Go to Google',
     link: 'https://www.google.com'
 }),

component_example_text

Image

Image provides functionality to render an image from a file, string (SVG), buffer, or URL. Base64 strings need to be converted into a Buffer before they can be consumed by an Image component.

Hierarchy: BaseComponent / BaseLayoutComponent / Image

| Property | Type | Description | | --------- | ---------------- | ------------------------------------------------------------ | | source | string | Buffer | Image source to render | | stretch | bool | Indicates whether the image should be stretched to the provided width and height |

Example

Go to usage example | Go to sample PDF

const image = new Image({
    width: 100,
    height: 100,
    source: 'https://picsum.photos/300/300',
}),

component_example_image

RepeatVertical

RepeatVertical provides functionality to repeat a template for each value of a provided binding array. RepeatVertical requests new pages when its component generation functionality reaches the end of a page.

Hierarchy: BaseComponent / BaseLayoutComponent / RepeatVertical

| Property | Type | Description | | ---------- | ----------------- | ------------------ | | template | BaseComponent | Template to render |

Example

Go to usage example | Go to sample PDF

new RepeatVertical({
    binding: 'arrayValues',
    template: new Container({
        margin: new Offset({
            top: 5,
        }),
        border: new Border(),
        children: [
            new Text({
                margin: new Offset(5),
                text: 'Array value: {{.}}',
            }),
        ],
    }),
})
...
const array = [];
for (let i = 0; i < 5; i++) {
    array.push(i);
}
...
const data = {
    arrayValues: array,
};

component_example_repeat_vertical

Table

Table provides functionality to render a table to the document. Table requests new pages when its component generation functionality reaches the end of a page. Components without specified widths are sized automatically according to available space.

Hierarchy: BaseComponent / BaseLayoutComponent / Table

| Property | Type | Description | | ---------------------- | --------------------------------------------- | ----------------------------------------------------- | | headerStyle | Style | Style to apply to header cells | | cellStyle | Style | Style to apply to content cells | | alternativeCellStyle | Style | Style override to apply to content cells of even rows | | columns | [Column] | Defines table columns and data bindings |

Example

Go to usage example | Go to sample PDF

new Table({
    binding: 'tableValues',
    border: new Border(new BorderSide({ thickness: 1 })),
    headerStyle: {
        fontWeight: FontWeight.bold,
        border: new Border(),
    },
    alternativeCellStyle: {
        backgroundColor: '#ccc',
    },
    columns: [
        {
            property: 'id',
            width: 50,
            text: '#',
        },
        {
            property: 'invoice_id',
            text: 'Invoice',
            fx: (index, record, value) => {
                return '#' + value.toString().padStart(6, '0');
            },
        },
        {
            property: 'date_created',
            width: 100,
            text: 'Date',
        },
        {
            property: 'amount',
            width: 70,
            cellStyle: {
                textAlignment: TextAlignment.right,
            },
            text: 'Amount',
            fx: (index, record, value) => {
                return value.toFixed(2);
            }
        }
    ]
})
...
const data = {
    tableValues: [
        { "id": 12, "invoice_id": 23, "date_created": "2021-04-09T05:54:03.000Z", "amount": 60 },
        ...
    ]
};

component_example_table

Enums

Alignment

Alignment.start
Alignment.end
Alignment.middle
Alignment.fill

FontWeight

FontWeight.bold
FontWeight.normal

Layout

Layout.none
Layout.spaceEvenly
Layout.sizeEvenly

TextAlignment

TextAlignment.left
TextAlignment.right
TextAlignment.center
TextAlignment.justify

PageSize

PageSize defines preset page dimensions to apply to the size property of a Page component.

| A | B | C | R & SRA | Other | | -------------- | -------------- | -------------- | --------------- | -------------------- | | PageSize.A0 | PageSize.B0 | PageSize.C0 | PageSize.RA0 | PageSize.EXECUTIVE | | PageSize.A1 | PageSize.B1 | PageSize.C1 | PageSize.RA1 | PageSize.LEGAL | | PageSize.A2 | PageSize.B2 | PageSize.C2 | PageSize.RA2 | PageSize.LETTER | | PageSize.A3 | PageSize.B3 | PageSize.C3 | PageSize.RA3 | PageSize.TABLOID | | PageSize.A4 | PageSize.B4 | PageSize.C4 | PageSize.RA4 | PageSize.FOLIO | | PageSize.A5 | PageSize.B5 | PageSize.C5 | PageSize.SRA0 | PageSize.2A0 | | PageSize.A6 | PageSize.B6 | PageSize.C6 | PageSize.SRA1 | PageSize.4A0 | | PageSize.A7 | PageSize.B7 | PageSize.C7 | PageSize.SRA2 | | | PageSize.A8 | PageSize.B8 | PageSize.C8 | PageSize.SRA3 | | | PageSize.A9 | PageSize.B9 | PageSize.C9 | PageSize.SRA4 | | | PageSize.A10 | PageSize.B10 | PageSize.C10 | | |

Models

Border

Border constructor requires an object containing the properties listed below. Alternatively the Border constructor can be supplied with a single parameter of type or schema BorderSide to apply to all sides of the border.

| Property | Type | Description | | -------- | -------------- | ----------------------- | | left | BorderSide | Left border to render | | top | BorderSide | Top border to render | | right | BorderSide | Right border to render | | bottom | BorderSide | Bottom border to render |

BorderSide

| Property | Type | Description | | ----------- | ------ | ---------------------------- | | thickness | number | Thickness of the border side | | color | string | Color of the border side |

Column

| Property | Type | Description | | ---------------------- | ----------------------------------------- | ------------------------------------------------------------ | | property | string | Property of table record to display | | text | string | Heading value to display | | width | number | Width of columnA value of 1 is used if omittedValues <= 1 are used to determine fractional size of column | | headerStyle | Style | Style to apply to header cell of the column | | cellStyle | Style | Style to apply to data cells of the column | | alternativeCellStyle | Style | Style to apply to data cells of even rows of the column | | fx | function | Preprocessor function to run before data is displayed(index, record, value) => alteredValue |

Offset

Offset constructor requires an object containing the properties listed below. Alternatively the Offset constructor can be supplied with a single parameter of type number to apply to all sides of the offset.

| Property | Type | Description | | -------- | ------ | ---------------------- | | left | number | Left offset to apply | | top | number | Top offset to apply | | right | number | Right offset to apply | | bottom | number | Bottom offset to apply |

Size

| Property | Type | Description | | -------- | ------ | ------------ | | width | number | Width value | | height | number | Height value |

Style

| Property | Type | Description | | ----------------- | --------------------------------------------------------- | ------------------------------- | | border | Border | Border value | | backgroundColor | string | Background color value | | color | string | Foreground color value | | fontSize | number | Font size value | | fontWeight | FontWeight | Font weight value | | textAlignment | TextAlignment | Horizontal text alignment value |

Binding

Components can bind to a specific property in a data source and provide that binding to its content. String interpolation can be performed by enclosing the binding string in curly brace sets ({{binding_string}}).

For the purposes of binding page information to the header and footer components, the data source should not contain properties named pageNumber or pageCount.

An example of binding can be found here.

new Container({
    binding: 'tier1',
    children: [
        new StackVertical({
            binding: 'tier2',
            children: [
                new Text({
                    binding: 'tier3.tier4',
                    margin: new Offset(10),
                    text: 'date = {{tier5.date}}',
                })
            ]
        })
    ]
})
...
const data = {
    tier1: {
        tier2: {
            tier3: {
                tier4: {
                    tier5: {
	                    date: new Date(),
                    }
                }
            }
        }
    }
};

component_example_binding

Header & Footer

Header templates should set in the firstPageHeader and header properties of a Page component. If firstPageHeader has a value, its template will be rendered on the first page of the document, subsequent pages will use header if present. The header template must have an explicit height value set.

The footer template should be set in the footer property of a Page component. The footer template must have an explicit height value set.

Components within the header and footer templates can bind to the pageNumber and pageCount properties of the data source to display the current page information.

...
footer: new Container({
    height: 50,
    children: [
        new Text({
            text: 'Page {{pageNumber}} of {{pageCount}}',
        }),
    ],
}),
...

example_footer_page_number

An example of a header and footer can be found here (PDF).

Multiple Templates

Rendering multiple templates to a single document can be achieved by defining multiple Page templates, and passing the resulting doc as a parameter to the next rendering. Only the last template should define header and footer content, preceding templates should contain empty components which define only the height property. All headers and footers should share the same respective height values. As an alternative to creating empty header and footer components, the preceding pages may specify a larger margin to consume the space.

const template1 = new Page({
    size: PageSize.A4,
    margin: new Offset(50),
    header: new Container({
        height: 50,
    }),
    footer: new Container({
        height: 50,
    }),
    children: [
        ...
    ],
});
const template2 = new Page({
    size: PageSize.A4,
    margin: new Offset(50),
    header: new Container({
        height: 50,
        children: [
            ...
        ],
    }),
    footer: new Container({
        height: 50,
        children: [
            ...
        ],
    }),
    children: [
        ...
    ],
});
...
let doc = ReplyPDF.generateDocument({
    data: data1,
    template: template1,
});

doc = ReplyPDF.generateDocument({
    doc: doc, // provide existing document object
    data: data2,
    template: template2,
});

An example of using multiple templates can be found here (PDF).

Custom Fonts

Custom fonts can be embedded into the PDF document by supplying font names with accompanying buffers containing the content of the font. The buffers can be loaded from a file or from a URL by making a HTTP request. For the fontWeight property to function correctly, the bold version of a font should be named after the regular version + '-Bold'.

Font buffers should be added to the generateDocument function parameter as shown in the example below:

let doc = ReplyPDF.generateDocument({
    fonts: {
        'Robinette': fs.readFileSync('examples/fonts/Robinette.ttf'),
        'GrandAutoDemo': fs.readFileSync('examples/fonts/GrandAutoDemoRegular.ttf'),
        'PlayfairDisplay': fs.readFileSync('examples/fonts/PlayfairDisplay-Regular.ttf'),
        'PlayfairDisplay-Bold': fs.readFileSync('examples/fonts/PlayfairDisplay-Bold.ttf'),
    },
    template: template,
    debug: false,
});

An example of using custom fonts can be found here.

Charts

Charts can be included into the PDF document by including the chart as an image. Chart images can be generated by using some of the chart-to-image services listed below:

  • Image Charts (https://www.image-charts.com/)
  • Quick Chart (https://quickchart.io/)
  • Chart Works (https://www.chartworks.io/charts/image)

An example of including a chart into a PDF can be found here ( PDF).

Debugging

Debugging can be enabled on a document or component level. When debugging is enabled, a green border is drawn around the component outline, and a red border around the component margin.

debugging_example

When enabling debugging on component level, an annotation is added to the document describing the component and layout information.

debugging_annotations

Enabling debugging on document level:

let doc = ReplyPDF.generateDocument({
    ...
    debug: true,
    ...
});

Enabling debugging on component level:

var component = new ComponentTypeGoesHere({
    ...
    debug: true,
    ...
});

Tests

The file test.js contains code to run all the examples in this project. Each example generates a PDF file as output into the examples/outputs directory. To execute the tests, run the following command after cloning:

npm test

Pitfalls

Components that do not request new pages when content tries to overflow will continue to render content on the same page. The content of these components should ideally be assigned fixed sizes to prevent rendering content off of the page or content that overflows into the footer section.

Image rendering from URL is performed by making synchronous http calls. You may experience delays when building a document with a large number of URL based images.

Roadmap

General Enhancements

Improve multiple template rendering process to be able to render different headers and footers for each template.

Implement functionality to use existing PDF documents as the base to render the template onto. As this functionality is not supported by PDFKit, the merge functionality may be delegated to Hummus or HummusRecipe.

New Components

Barcode: Render a barcode to the document

PageBreak: Continue content rendering on a new page

Component Enhancements

| Component | Enhancements | | --------------- | ------------------------------------------------------------ | | Table | - Render column content based on a template, currently renders all cells as text- Configuration to prevent reprinting of header on new pages, currently headers are repeated on each new page | | Image | - Restructure image loading process to enable asynchronous calls to read data from a URL |