@devmm/puredocs-pdf
v1.0.1
Published
Fast, dependency-light PDF library for Node, Browser, Deno, Bun and edge runtimes. Read, create and manipulate PDF documents in pure TypeScript.
Maintainers
Readme
@devmm/puredocs-pdf
Fast, dependency-light PDF library for Node, Browser, Deno, Bun and edge runtimes. Read, create and manipulate PDF documents from TypeScript with a small, tree-shakeable API.
Status:
0.1.0— the read / create / manipulate core is implemented, tested and publishable. Advanced features (encryption, digital signatures, custom font embedding, rasterisation) are on the roadmap and are honestly marked as not-yet-available rather than promised.
Why this library
- Correct, interoperable compression.
FlateDecodeuses the platform-nativeCompressionStream/DecompressionStream(real zlib / RFC 1950). Files it writes are readable by every PDF viewer, and it reads Flate produced by any tool — no hand-rolled, non-standard deflate. - Random-access parser. Objects are located through the cross-reference table (classic tables and PDF 1.5+ xref streams + compressed object streams), the way real PDFs are structured — not by lexing the whole file into memory.
- Light dependency surface. Zero runtime dependencies; the heavy primitives (zlib, SHA) come from the runtime, not from bundled re-implementations.
- Universal. Pure TypeScript targeting standard Web APIs; runs anywhere modern JS runs.
Install
npm install @devmm/puredocs-pdfRequires Node ≥ 18 (or any runtime with CompressionStream).
Quick start
import { PDFDocument, rgb } from '@devmm/puredocs-pdf';
// Create
const doc = PDFDocument.create();
doc.setTitle('Hello');
const page = doc.addPage('A4');
page.drawText('Hello World', { x: 50, y: 750, size: 24, color: rgb(0, 0, 0) });
page.drawRectangle({ x: 40, y: 740, width: 200, height: 40, borderColor: rgb(0, 0, 1) });
const bytes = await doc.save();
// Read
const loaded = await PDFDocument.load(bytes);
console.log(loaded.pageCount, loaded.getTitle());
console.log(await loaded.getPage(0).getText());Merge, split, watermark
const merged = await PDFDocument.merge([docA, docB]);
merged.addWatermark('CONFIDENTIAL');
const [firstHalf, secondHalf] = await doc.split([[0, 5], [5, 10]]);Images
const img = await doc.embedPng(pngBytes); // or embedJpg / embedImage
page.drawImage(img, { x: 50, y: 400, width: 300, height: 200, opacity: 0.9 });Feature matrix
| Area | Supported now | Roadmap | |------|---------------|---------| | Parse structure | xref tables, xref streams, object streams, trailer, recovery scan | linearized fast-path | | Filters | FlateDecode, LZWDecode, ASCIIHex, ASCII85, RunLength, PNG/TIFF predictors | CCITT, JBIG2 | | Read | metadata, page geometry, text extraction | per-font CMap/ToUnicode, image extraction | | Create | pages, text (standard 14 fonts), shapes, images (JPEG/PNG+alpha), opacity | custom TrueType/OpenType embedding + subsetting | | Manipulate | merge, split, copy pages, remove, reorder, rotate, watermark | crop, page overlays, optimize/linearize | | Metadata | title/author/subject/keywords/creator/producer | XMP metadata streams | | Security | — | RC4/AES read+write, permissions | | Signatures | — | PKCS#7 sign + validate |
Anything not implemented throws a clear PDFNotSupportedError — the library never silently
produces wrong output.
Benchmarks
Real measurements (npm run bench), Node v22, on the developer machine. These are actual
numbers from the code in this repo — not targets. Run it yourself to reproduce.
| Task | ops/sec | avg | |------|--------:|----:| | create + save 100-page PDF | ~52 | ~19 ms | | load 100-page PDF | ~1500 | ~0.7 ms | | extract text from 100-page PDF | ~51 | ~20 ms | | flateEncode 256 KB | ~1600 | ~0.6 ms | | flateDecode 256 KB | ~1500 | ~0.7 ms |
To compare against pdf-lib / pdfjs, install them as devDependencies and add cases to
benchmarks/suite.ts; none are bundled so the default results stay honest.
API overview
PDFDocument.create(options?)/PDFDocument.load(bytes)doc.addPage(size),doc.getPage(i),doc.getPages(),doc.pageCountdoc.copyPages(src, indices?),PDFDocument.merge(docs),doc.split(ranges)doc.removePage(i),doc.reorderPages(order)doc.setTitle/…,doc.getTitle/…doc.embedJpg/embedPng/embedImage,doc.addWatermark(text, opts?)doc.extractText(),page.getText()page.drawText/drawRectangle/drawLine/drawCircle/drawImage,page.rotate(deg)page.measureText(text, size, font)
Full types ship with the package.
Development
npm install
npm test # vitest
npm run bench # tinybench
npm run build # tsup -> dist (ESM + CJS + d.ts)
npm run check:pack # publintLicense
MIT © devmm
