pretext-docgen

@shipitandpray/pretext-docgen

Live Demo GitHub

View Live Demo

Generate PDFs at 100x lower cost. Pure JS. No Puppeteer. No Chrome. Lambda/Workers/Edge.

Document generation engine that replaces Puppeteer for PDF creation. Pure JavaScript. Runs anywhere JS runs – AWS Lambda, Cloudflare Workers, Vercel Edge Functions, Deno Deploy, browsers.

npm install @shipitandpray/pretext-docgen

Quick Start

Generate an invoice in 10 lines:

import { createInvoice } from '@shipitandpray/pretext-docgen';
import { writeFile } from 'fs/promises';

const pdf = await createInvoice({
  company: { name: 'Acme Corp', address: '123 Main St, Dallas, TX' },
  client: { name: 'Widget Inc', address: '456 Oak Ave, Austin, TX' },
  invoiceNumber: 'INV-001',
  date: '2026-03-29',
  dueDate: '2026-04-28',
  items: [
    { description: 'Web Development', quantity: 40, unitPrice: 150 },
    { description: 'Design Services', quantity: 20, unitPrice: 125 },
  ],
  taxRate: 0.0825,
});

await writeFile('invoice.pdf', pdf);

Or use the fluent API for custom documents:

import { createDocument } from '@shipitandpray/pretext-docgen';

const pdf = await createDocument({ pageSize: 'a4', margins: 72 })
  .addHeading('Quarterly Report', 1)
  .addParagraph('Revenue grew 21% quarter over quarter.')
  .addTable({
    headers: [{ text: 'Metric' }, { text: 'Q3' }, { text: 'Q4' }],
    rows: [
      [{ text: 'Revenue' }, { text: '$4.2M' }, { text: '$5.1M' }],
      [{ text: 'Expenses' }, { text: '$3.1M' }, { text: '$3.3M' }],
    ],
    style: {
      headerStyle: { backgroundColor: '#1a237e', color: '#fff', fontWeight: 'bold' },
      alternateRowStyle: { backgroundColor: '#f5f5f5' },
      cellPadding: 8,
    },
    columnWidths: ['*', 100, 100],
  })
  .addList([
    { text: 'Enterprise revenue up 35%' },
    { text: 'Three new accounts signed' },
  ], { type: 'bullet' })
  .build();

Why Not Puppeteer?

Puppeteer works by spinning up a full Chromium browser to render HTML and call page.pdf(). This creates three problems:

  1. Cost. Each Lambda invocation loads a 250MB+ Chromium binary. At 1M docs/month, you’re paying $133/month just for compute.
  2. Cold starts. Chromium takes 5-12 seconds to cold start on Lambda. Your users wait.
  3. No edge. Puppeteer cannot run on Cloudflare Workers, Vercel Edge Functions, or Deno Deploy. You’re locked into traditional serverless.

pretext-docgen eliminates all three. Pure JavaScript. No binaries. No Chrome.


Cost Comparison

This is the killer number. At scale, the savings are enormous.

Metric pretext-docgen Puppeteer (Lambda) Puppeteer (EC2)
Lambda memory 128MB 1024MB N/A
Lambda duration ~50ms ~8,000ms N/A
Cost per invocation $0.0000001 $0.000133 N/A
1M docs/month $0.10 $133 ~$200
10M docs/month $1.00 $1,330 ~$800
Cold start 100ms 8-12s N/A
Edge deployable Yes No No

Assumptions: AWS Lambda us-east-1 pricing, 2026. Puppeteer uses chromium-min layer. pretext-docgen uses 128MB minimum allocation. Duration measured on invoice generation workload.


Feature Comparison

Feature pretext-docgen Puppeteer pdfkit jsPDF @react-pdf/renderer
Pure JS (no binaries) Yes No No (optional) Yes Yes
Edge/Worker support Yes No No Yes No
Text layout engine Yes Yes (Chrome) Manual Manual Partial
Tables (auto-width) Yes Yes (HTML) Manual Plugin Limited
Widow/orphan control Yes Yes (CSS) No No No
Headers/footers Yes (per-page) Yes (CSS) Manual Manual Partial
Templates 3 built-in None None None None
Font subsetting Yes Yes Yes Limited Yes
Bundle size ~80KB gzip 50MB+ 200KB 300KB 300KB
Lambda cold start 100ms 8-12s 500ms 200ms 1s
Cost at 1M docs/mo $0.10 $133 ~$5 N/A ~$5

Performance Targets

Metric Target vs Puppeteer
Simple invoice < 50ms 5-10 seconds
10-page report < 200ms 10-20 seconds
100-page report with tables < 2s 30-60 seconds
Memory (invoice) < 20MB 500MB+
Cold start (Lambda) < 100ms 5-10 seconds
Bundle size < 80KB gzip 50MB+ (Chromium)
PDF output (invoice) < 50KB ~200KB

Templates

Invoice

import { createInvoice } from '@shipitandpray/pretext-docgen';

const pdf = await createInvoice({
  company: { name: 'Acme Corp', address: '123 Main St', phone: '(555) 123-4567' },
  client: { name: 'Widget Inc', address: '456 Oak Ave' },
  invoiceNumber: 'INV-2026-0042',
  date: '2026-03-29',
  dueDate: '2026-04-28',
  items: [
    { description: 'Consulting', quantity: 10, unitPrice: 200 },
  ],
  taxRate: 0.0825,
  currency: '$',
  notes: 'Thank you for your business!',
});

Report

import { createReport } from '@shipitandpray/pretext-docgen';

const pdf = await createReport({
  title: 'Annual Report',
  author: 'Research Team',
  date: '2026',
  includeTableOfContents: true,
  sections: [
    { title: 'Introduction', content: 'This report covers...' },
    { title: 'Findings', content: 'Key results include...',
      subsections: [
        { title: 'Methodology', content: 'We used...' },
      ],
    },
  ],
});

Resume

import { createResume } from '@shipitandpray/pretext-docgen';

const pdf = await createResume({
  name: 'Jane Smith',
  title: 'Senior Software Engineer',
  email: 'contact [at] janesmith.dev',
  phone: '(555) 987-6543',
  location: 'San Francisco, CA',
  summary: 'Full-stack engineer with 8 years of experience.',
  experience: [{
    company: 'TechCorp',
    title: 'Senior Engineer',
    startDate: 'Jan 2022',
    endDate: 'Present',
    bullets: ['Led microservices migration', 'Built data pipeline processing 10M events/day'],
  }],
  education: [{ school: 'MIT', degree: 'BS Computer Science', graduationDate: '2018' }],
  skills: ['TypeScript', 'React', 'Node.js', 'Python', 'AWS', 'Docker'],
});

API Reference

Document Builder

Method Description
createDocument(options?) Create a new document builder
.addText(text, style?) Add a text block
.addHeading(text, level, style?) Add a heading (1-6)
.addParagraph(text, style?) Add a paragraph (text with bottom margin)
.addTable(config) Add a table
.addImage(src, options?) Add an image (Uint8Array or base64)
.addList(items, options?) Add a bullet/numbered list
.addDivider(style?) Add a horizontal rule
.addSpacer(height) Add vertical space
.addPageBreak() Force a page break
.setHeader(config) Set page header
.setFooter(config) Set page footer
.registerFont(name, data, options?) Register a custom font
.pushStyle(style) Push a style scope
.popStyle() Pop the current style scope
.build() Generate PDF as Uint8Array
.buildBase64() Generate PDF as base64 string

Document Options

{
  pageSize?: 'letter' | 'a4' | 'a3' | 'a5' | 'legal' | 'tabloid' | { width, height },
  margins?: { top, right, bottom, left } | number,
  defaultFont?: { family?, size? },
  defaultStyle?: StyleConfig,
  metadata?: { title?, author?, subject?, keywords?, creator? },
  header?: { height, render(ctx) },
  footer?: { height, render(ctx) },
  compress?: boolean,
}

Style Config

{
  fontFamily?: string,        // 'Helvetica' (default), 'Times', 'Courier'
  fontSize?: number,          // in points (default: 12)
  fontWeight?: 'normal' | 'bold',
  fontStyle?: 'normal' | 'italic',
  color?: string,             // hex, rgb(), or named color
  backgroundColor?: string,
  lineHeight?: number,        // multiplier (default: 1.2)
  textAlign?: 'left' | 'center' | 'right' | 'justify',
  marginTop?: number,
  marginBottom?: number,
  paddingTop?: number,
  paddingBottom?: number,
}

Table Config

{
  headers?: TableCell[],
  rows: TableCell[][],
  columnWidths?: (number | 'auto' | '*')[],  // fixed, auto-fit, or flex
  style?: {
    headerStyle?: StyleConfig,
    rowStyle?: StyleConfig,
    alternateRowStyle?: StyleConfig,
    cellPadding?: number,
    borderWidth?: number,
    borderColor?: string,
  },
  repeatHeaderOnNewPage?: boolean,
}

Utilities

import { mergePDFs, convertUnits } from '@shipitandpray/pretext-docgen';

// Merge multiple PDFs
const combined = await mergePDFs([pdf1, pdf2, pdf3]);

// Convert units (in, cm, mm, pt, px)
const points = convertUnits(1, 'in', 'pt'); // 72

Deployment Guides

AWS Lambda

import { createInvoice } from '@shipitandpray/pretext-docgen';

export const handler = async (event) => {
  const data = JSON.parse(event.body);
  const pdf = await createInvoice(data);
  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/pdf' },
    body: Buffer.from(pdf).toString('base64'),
    isBase64Encoded: true,
  };
};

Memory: 128MB. Duration: ~50ms. Cost: $0.0000001 per invocation.

Cloudflare Workers

import { createInvoice } from '@shipitandpray/pretext-docgen';

export default {
  async fetch(request) {
    const data = await request.json();
    const pdf = await createInvoice(data);
    return new Response(pdf, {
      headers: {
        'Content-Type': 'application/pdf',
        'Content-Disposition': 'attachment; filename="invoice.pdf"',
      },
    });
  },
};

Vercel Edge Functions

import { createDocument } from '@shipitandpray/pretext-docgen';

export const config = { runtime: 'edge' };

export default async function handler(req) {
  const pdf = await createDocument({ pageSize: 'a4' })
    .addHeading('Edge-Generated PDF', 1)
    .addParagraph('Generated on the edge, close to the user.')
    .build();
  return new Response(pdf, {
    headers: { 'Content-Type': 'application/pdf' },
  });
}

Browser

import { createDocument } from '@shipitandpray/pretext-docgen';

const pdf = await createDocument({ pageSize: 'letter' })
  .addHeading('Client-Side PDF', 1)
  .addParagraph('Generated entirely in the browser. No server round-trip.')
  .build();

const blob = new Blob([pdf], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url);

Migration from Puppeteer

  1. Remove Puppeteer dependency: npm uninstall puppeteer puppeteer-core
  2. Install pretext-docgen: npm install @shipitandpray/pretext-docgen
  3. Replace HTML template with createDocument() fluent API or a built-in template
  4. Remove browser launch code – no more puppeteer.launch(), page.goto(), page.pdf()
  5. Deploy to edge – your Lambda function now works on Workers/Edge too

Before:

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(htmlTemplate);
const pdf = await page.pdf({ format: 'A4' });
await browser.close();

After:

const pdf = await createDocument({ pageSize: 'a4' })
  .addHeading('Report', 1)
  .addParagraph(content)
  .addTable(tableData)
  .build();

Development

npm install
npm run build          # Build with tsup (ESM + CJS + types)
npm test               # Run vitest tests
npm run dev            # Watch mode
npm run demo           # Launch demo at localhost

Dependencies

Zero native dependencies. Zero binary dependencies. Runs anywhere JavaScript runs.

License

MIT


Built by ShipItAndPray