SkillAgentSearch skills...

Tinypdf

Minimal PDF creation library. <400 LOC, zero dependencies, makes real PDFs.

Install / Use

/learn @Lulzx/Tinypdf
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

tinypdf

npm size license

Minimal PDF creation library. <400 LOC, zero dependencies, makes real PDFs.

npm install tinypdf

Invoice Example

View sample PDF — Generated with ~50 lines of code


Why tinypdf?

| | tinypdf | jsPDF | |--|---------|-------| | Size | 3.3 KB | 229 KB | | Dependencies | 0 | 2 |

~70x smaller. We removed TTF fonts, PNG/SVG, HTML-to-PDF, forms, encryption, and compression. What's left is the 95% use case: put text and images on a page.

Build with it

Invoices, receipts, reports, shipping labels, tickets, certificates, contracts, data exports

Features

| Feature | Description | |---------|-------------| | Text | Helvetica, any size, hex colors, align left/center/right | | Shapes | Rectangles and lines | | Images | JPEG (photos, logos, signatures) | | Links | Clickable URLs with optional underline | | Pages | Multiple pages, custom sizes | | Markdown | Convert markdown to PDF with headers, lists, rules |

Not included

Custom fonts, PNG/GIF/SVG, vector graphics, forms, encryption, compression, HTML-to-PDF

Need those? Use jsPDF or pdf-lib.


Quick start

import { pdf } from 'tinypdf'
import { writeFileSync } from 'fs'

const doc = pdf()

doc.page((ctx) => {
  ctx.rect(50, 700, 200, 40, '#2563eb')           // blue rectangle
  ctx.text('Hello PDF!', 60, 712, 24, { color: '#ffffff' })
  ctx.line(50, 680, 250, 680, '#000000', 1)       // black line
})

writeFileSync('output.pdf', doc.build())

Add images

import { readFileSync } from 'fs'

doc.page((ctx) => {
  const logo = new Uint8Array(readFileSync('logo.jpg'))
  ctx.image(logo, 50, 700, 100, 50)
})

Add links

import { pdf, measureText } from 'tinypdf'

doc.page((ctx) => {
  const text = 'Visit Example.com'
  const y = 700
  ctx.text(text, 50, y, 14, { color: '#0066cc' })
  ctx.link('https://example.com', 50, y - 4, measureText(text, 14), 18, { underline: '#0066cc' })
})

Measure text width

import { measureText } from 'tinypdf'

measureText('Hello', 12) // => 27.34 (points)

Markdown to PDF

import { markdown } from 'tinypdf'
import { writeFileSync } from 'fs'

const pdf = markdown(`
# Hello World

A minimal PDF from markdown.

## Features
- Headers (h1, h2, h3)
- Bullet lists
- Numbered lists
- Horizontal rules

---

Automatic word wrapping and pagination included.
`)

writeFileSync('output.pdf', pdf)

API

pdf()                                      // create document
doc.page(callback)                         // add page (612×792 default)
doc.page(width, height, callback)          // add page with custom size
doc.build()                                // returns Uint8Array

ctx.text(str, x, y, size, options?)        // options: { color, align, width }
ctx.rect(x, y, w, h, fill)                 // filled rectangle
ctx.line(x1, y1, x2, y2, stroke, width?)   // line
ctx.image(jpegBytes, x, y, w, h)           // JPEG image
ctx.link(url, x, y, w, h, options?)        // options: { underline }

measureText(str, size)                     // text width in points
markdown(str, options?)                    // options: { width, height, margin }

Full example

<details> <summary>Invoice generator (~50 lines)</summary>
import { pdf } from 'tinypdf'
import { writeFileSync } from 'fs'

const doc = pdf()

doc.page(612, 792, (p) => {
  const margin = 40, pw = 532

  // Header
  p.rect(margin, 716, pw, 36, '#2563eb')
  p.text('INVOICE', 55, 726, 24, { color: '#fff' })
  p.text('#INV-2025-001', 472, 728, 12, { color: '#fff' })

  // Company & billing info
  p.text('Acme Corporation', margin, 670, 16)
  p.text('123 Business Street', margin, 652, 11, { color: '#666' })
  p.text('New York, NY 10001', margin, 638, 11, { color: '#666' })

  p.text('Bill To:', 340, 670, 12, { color: '#666' })
  p.text('John Smith', 340, 652, 14)
  p.text('456 Customer Ave', 340, 636, 11, { color: '#666' })
  p.text('Los Angeles, CA 90001', 340, 622, 11, { color: '#666' })

  // Table
  p.rect(margin, 560, pw, 25, '#f3f4f6')
  p.text('Description', 50, 568, 11)
  p.text('Qty', 310, 568, 11)
  p.text('Price', 380, 568, 11)
  p.text('Total', 480, 568, 11)

  const items = [
    ['Website Development', '1', '$5,000.00', '$5,000.00'],
    ['Hosting (Annual)', '1', '$200.00', '$200.00'],
    ['Maintenance Package', '12', '$150.00', '$1,800.00'],
  ]

  let y = 535
  for (const [desc, qty, price, total] of items) {
    p.text(desc, 50, y, 11)
    p.text(qty, 310, y, 11)
    p.text(price, 380, y, 11)
    p.text(total, 480, y, 11)
    p.line(margin, y - 15, margin + pw, y - 15, '#e5e7eb', 0.5)
    y -= 30
  }

  // Totals
  p.line(margin, y, margin + pw, y, '#000', 1)
  p.text('Subtotal:', 380, y - 25, 11)
  p.text('$7,000.00', 480, y - 25, 11)
  p.text('Tax (8%):', 380, y - 45, 11)
  p.text('$560.00', 480, y - 45, 11)
  p.rect(370, y - 75, 202, 25, '#2563eb')
  p.text('Total Due:', 380, y - 63, 12, { color: '#fff' })
  p.text('$7,560.00', 480, y - 63, 12, { color: '#fff' })

  // Footer
  p.text('Thank you for your business!', margin, 80, 12, { align: 'center', width: pw, color: '#666' })
  p.text('Payment due within 30 days', margin, 62, 10, { align: 'center', width: pw, color: '#999' })
})

writeFileSync('invoice.pdf', doc.build())
</details>

License

MIT

View on GitHub
GitHub Stars1.6k
CategoryFinance
Updated56m ago
Forks68

Languages

TypeScript

Security Score

100/100

Audited on Mar 21, 2026

No findings