@push.rocks/smartpdf 📄✨
Transform HTML, websites, and PDFs into beautiful documents and images with just a few lines of code.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
🚀 Why SmartPDF?
SmartPDF is your Swiss Army knife for PDF operations in Node.js. Whether you're generating invoices from HTML, snapshotting web pages, merging documents, or converting PDF pages to images — SmartPDF handles it all through a clean, async-first TypeScript API backed by headless Chromium.
✨ Features at a Glance
| Feature | Description |
|---|---|
| 📝 HTML → PDF | Render any HTML string (with full CSS) into an A4-sized PDF |
| 🌐 Website → PDF | Capture a live URL as a PDF — one page the size of its document, or the full-page scroll |
| 🔀 PDF Merging | Combine multiple PDF buffers into a single document |
| 🖼️ PDF → Images | Convert PDF pages to PNG, WebP, or progressive JPEG |
| 🧮 PDF → Pixels | Render pages to raw RGB or gray pixels at an exact DPI, one page at a time, without a browser (for raster printers) |
| 📑 Text Extraction | Pull raw text content from any PDF buffer |
| 🔍 OCR | Add a searchable text layer to scanned PDFs with the system's ocrmypdf |
| 🔌 Smart Port Management | Automatic port allocation so multiple instances never collide |
| 🎛️ DPI Control | Built-in scale constants for screen, high-quality, and print resolutions |
| 🌐 BYO Browser | Optionally pass your own Puppeteer Browser instance |
📦 Installation
pnpm add @push.rocks/smartpdf
Prerequisites: SmartPDF requires Node.js 22.13 or newer and uses headless Chromium through SmartBrowser 5's automation entry and Puppeteer 25. On most systems Chromium is handled automatically. If you run into browser-launch issues (CI, Docker, etc.), make sure the required system libraries are installed and review the Puppeteer troubleshooting guide.
PDF engine: merging and assembling PDFs, counting their pages, extracting their text and rendering their pages to pixels run in
smartpdf-bin, the Rust core of this package (lopdf and hayro), as a child process that@push.rocks/smartrustdrives. The package ships it for Linux on amd64 and arm64 (static musl builds that need no system libraries). On other platforms these methods need asmartpdf-binbuilt fromrust/(cargo build --release) and named bySMARTPDF_RUST_BINARY. The engine starts on first use and stops once it has had nothing to do for a second; a document that crashes or hangs it rejects the call, never your process.
Installed with it: PDF → image rendering in the browser uses PDF.js from
pdfjs-dist(Apache-2.0).pdfjs-distdeclares@napi-rs/canvas(MIT; its native binary bundles Google Skia, BSD-3-Clause) as an optional dependency, so a package manager that installs optional dependencies (the default) also fetches that binary for your platform. SmartPDF does not use it: PDF.js renders in the headless browser, and text and page rasters come from the PDF engine.
Optional system tools for OCR:
ocrPdfBytes()runs OCRmyPDF (MPL-2.0), which drives Tesseract (Apache-2.0) and Ghostscript (AGPL-3.0 or commercial). They are not npm dependencies: install them with the system package manager (e.g.apt install ocrmypdf tesseract-ocr-eng ghostscript) together with the Tesseract language packs you need. SmartPDF only starts them as separate processes; nothing of them is linked into or shipped with this package. Every other feature works without them.
🎯 Quick Start
import { SmartPdf } from '@push.rocks/smartpdf';
import * as fs from 'fs';
// 1. Create and start
const smartPdf = await SmartPdf.create();
await smartPdf.start();
// 2. Generate a PDF from HTML
const pdf = await smartPdf.getA4PdfResultForHtmlString(`
<h1>Hello, PDF World! 🌍</h1>
<p>Generated with SmartPDF.</p>
`);
// 3. Write to disk
fs.writeFileSync('my-first.pdf', pdf.buffer);
// 4. Clean up
await smartPdf.stop();
Every method returns an IPdf object:
interface IPdf {
id: string | null; // Unique identifier
name: string; // Filename
buffer: Buffer; // Raw PDF bytes
metadata?: {
textExtraction?: string; // Extracted text (when available)
};
}
📚 How It Works
SmartPDF spins up a lightweight HTTP server (via @push.rocks/smartserve) bound to localhost and a headless Chromium browser. When you call a generation method:
- Your HTML is registered internally and served at
http://localhost:{port}/{id} - Puppeteer navigates to that URL, waits for the page to fully render, and captures a PDF
- A header-based security check ensures only the correct content is captured
- The server and browser are torn down when you call
stop()
This architecture means you get pixel-perfect CSS rendering, web font support, and full JavaScript execution — the same rendering engine that powers Chrome.
🏗️ Instance Management
const smartPdf = await SmartPdf.create();
await smartPdf.start();
// ... your operations ...
await smartPdf.stop();
For production use, wrap in try/finally:
const smartPdf = await SmartPdf.create();
try {
await smartPdf.start();
// ... generate PDFs ...
} finally {
await smartPdf.stop();
}
SmartPDF leaves SIGINT, SIGTERM and SIGHUP to your program: the browser it launches keeps running when the process receives one of them, so a shutdown handler can finish the PDFs in flight and then call stop(). A program without such a handler ends as Node.js does on these signals, and the browser ends with it.
🔌 Smart Port Allocation
Run multiple instances without conflicts:
// Each instance auto-selects a free port (default range: 20000–30000)
const instance1 = new SmartPdf();
const instance2 = new SmartPdf();
await instance1.start(); // e.g. port 20000
await instance2.start(); // e.g. port 20001
console.log(instance1.serverPort); // 20000
console.log(instance2.serverPort); // 20001
// Custom range
const custom = new SmartPdf({ portRangeStart: 4000, portRangeEnd: 5000 });
// Or pin a specific port
const pinned = new SmartPdf({ port: 3000 });
start() takes a port by listening on it and moves on to the next port of the range when it is in use, so instances that start at the same time, in one process or in several, each get a port of their own. If a specific port is already in use, start() throws an error immediately instead of silently failing.
🌐 Bring Your Own Browser
Pass an existing Puppeteer Browser instance — SmartPDF won't close it when you call stop().
The browser must reach http://localhost:<serverPort> of the process running SmartPDF: HTML → PDF loads your HTML from there, and PDF → image loads the render page, PDF.js and its assets from there too. A browser that runs elsewhere (another host, or a container with its own network) cannot render.
pnpm add @push.rocks/smartpdf @push.rocks/smartbrowser
import { SmartPdf } from '@push.rocks/smartpdf';
import { getEnvAwareBrowserInstance } from '@push.rocks/smartbrowser/automation';
const browser = await getEnvAwareBrowserInstance();
const smartPdf = await SmartPdf.create();
await smartPdf.start(browser); // uses your browser
await smartPdf.stop(); // server stops, browser stays open
await browser.close(); // you manage browser lifecycle
🎨 PDF Generation
📝 HTML → A4 PDF
Renders at a 794×1122 viewport (A4 at 96 DPI) with full CSS support:
const pdf = await smartPdf.getA4PdfResultForHtmlString(`
<style>
body { font-family: 'Helvetica', sans-serif; margin: 40px; }
.header {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white; padding: 30px; border-radius: 10px; text-align: center;
}
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f5f5f5; }
</style>
<div class="header">
<h1>Invoice #2024-001</h1>
</div>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
<tr><td>Widget Pro</td><td>5</td><td>$49.99</td></tr>
<tr><td>Gizmo Ultra</td><td>2</td><td>$129.99</td></tr>
</table>
`);
fs.writeFileSync('invoice.pdf', pdf.buffer);
⏱️ HTML its scripts render: readySelector
By default the page is printed once its network has been idle for half a second (networkidle2). That is no sign
that the page's scripts have finished, and for a page that renders itself (a web component that lays out a document)
it is often later than needed or earlier than right. Let the page say when it is ready instead: its scripts make a
selector match, and the page is printed as soon as it has loaded and the selector matches:
const pdf = await smartPdf.getA4PdfResultForHtmlString(
`<my-invoice data="..."></my-invoice><script type="module">/* sets the attribute "rendered" when done */</script>`,
{ readySelector: 'my-invoice[rendered]' },
);
- The page's own network does not hold the print back: a request that stays open after the page is ready does not delay it.
- A page that does not match the selector within
readyTimeoutMs(default 30,000) fails the render with an error that names the selector and the errors the page threw. - Not with
network: 'blocked', which runs no JavaScript: the combination is refused.
🔒 HTML with text nobody vouches for: network: 'blocked'
By default the page loads what it references, as a browser would. When the HTML carries text a person or a program wrote (a note, a comment, an address), render it with its network blocked, so an image or a stylesheet in that text cannot make the server fetch an address:
const pdf = await smartPdf.getA4PdfResultForHtmlString(reportHtml, { network: 'blocked' });
- Every request but a
data:URI is refused before it leaves the browser: images, stylesheets, fonts, frames and prefetches, and the page is offline. Embed the images, stylesheets and fonts the PDF needs asdata:URIs; a remote reference is not loaded. - The page renders in a browser context of its own whose connections go to a proxy of the render's own that closes
them. Chrome opens a connection ahead of every frame navigation (
<iframe>,<object>,<embed>) before request interception, network emulation or a content security policy apply; through that proxy none of them reaches a host the HTML names. - The document is sandboxed (
Content-Security-Policy: sandbox) and runs no JavaScript: no<meta http-equiv="refresh">replaces it, no form is submitted, and no script opens a channel requests do not cover (a WebSocket, a WebRTC connection). Frames stay empty instead of showing the browser's error page.
🌐 Website → PDF
Two methods depending on your needs:
// Standard capture — uses the document's own dimensions
const pdf = await smartPdf.getPdfResultForWebsite('https://example.com');
// Full-page capture — scrolls to bottom, captures everything as a single page
const fullPdf = await smartPdf.getFullWebsiteAsSinglePdf('https://example.com');
getPdfResultForWebsite uses a 1980×1200 viewport and prints one page the size of the document (the larger of the
root's and the body's scroll width and height). getFullWebsiteAsSinglePdf uses a 1920px-wide viewport, grows it to the
document's scroll height and prints a single tall page.
Both print the page a website settles on. They wait for it to load (networkidle2) and then for one more second; a
navigation of its own in that time — a script's redirect once the page has loaded — is followed to the page it leads
to, up to five times. A navigation that leads to no document (no content, a download, a failed request) leaves the
page as it is. A navigation that starts later still, while the page is measured, is followed once as well. An HTTP
redirect or a navigation while the page loads lands where it leads, as before.
🔀 Merge Multiple PDFs
Combine any number of PDF buffers into one document, every page of each in order:
const invoice = await smartPdf.readFileToPdfObject('./invoice.pdf');
const terms = await smartPdf.readFileToPdfObject('./terms.pdf');
const appendix = await smartPdf.getA4PdfResultForHtmlString('<h1>Appendix</h1>...');
const merged = await smartPdf.mergePdfs([
invoice.buffer,
terms.buffer,
appendix.buffer,
]);
fs.writeFileSync('complete-package.pdf', merged);
- Runs in the PDF engine; no browser is needed. An entry that is not a readable PDF is refused, naming its place in the list (
PDF 2: the PDF could not be read.), and so is an encrypted PDF. An empty list gives a PDF without pages.
🧩 Assemble a PDF from Pages
Build one PDF from single pages of other PDFs and from pictures, in any order, each turned by a quarter turn if asked. No browser is needed:
const assembled = await smartPdf.assemblePdf([
{ kind: 'pdfPage', pdf: firstScan, pageNumber: 1 },
{ kind: 'pdfPage', pdf: secondScan, pageNumber: 3, turn: 180 }, // the sharper copy, upside down in the feeder
{ kind: 'image', image: photoJpeg, mimeType: 'image/jpeg', dpi: 300 }, // a 2480 × 3508 photo is an A4 page
]);
console.log(await smartPdf.getPdfPageCount(assembled)); // 3
- A PDF page keeps its size, content and annotations; its own turn and the one asked for add up. A PDF named by several pages is read once.
- A picture (JPEG or PNG) becomes a page of its own:
dpi(72 when not given) decides its size in points (pixels × 72 / dpi). - An empty list, a page number a PDF does not have, a turn that is not 0, 90, 180 or 270, a resolution that is no positive number, or bytes that are not a readable PDF or picture are refused; the message names the page of the list (
Page 2: the PDF has no page 4; it has 3.). getPdfPageCount(pdf)reads how many pages a PDF has without rendering it or reading its text.- Runs in the PDF engine (lopdf). Objects that several copied pages of one PDF share are copied once, and a link to a page that is not part of the result points nowhere instead of pulling that page in. An encrypted PDF is refused. The result carries no creation date, so the same pages give the same bytes.
📑 Read a PDF from Disk
const pdfObject = await smartPdf.readFileToPdfObject('./document.pdf');
console.log(pdfObject.name); // "document.pdf"
console.log(pdfObject.buffer); // <Buffer ...>
📖 Extract Text
Pull the text of every page from a PDF buffer. No browser is needed, so start() is not required:
const text = await smartPdf.extractTextFromPdfBuffer(pdf.buffer, {
maxPages: 50, // refuse longer documents before any text is read
signal: AbortSignal.timeout(30_000), // stop the work
});
console.log(text);
- Format: the glyphs of each page in the order of its content stream, joined as PDF.js joined its text items (which SmartPDF used before): a line break (
\n) where a line ends, one space for a gap of about a space, and a blank line (\n\n, exported asPDF_TEXT_PAGE_SEPARATOR) between pages. Works best with text-based PDFs; scanned documents have no text to read. - Per page:
extractTextPagesFromPdfBuffer(buffer, opts?)returns the text of each page on its own, one entry a page (empty for a page without a text layer): whatextractTextFromPdfBufferjoins. It takes the same options. - Safety: the text is read by the PDF engine (hayro, Rust) in its own process, with a deadline: only the pages' content is interpreted, no page is rendered, and nothing a document carries runs as code. A document that crashes or hangs the engine rejects the call.
- Limits:
maxPagesrefuses a document with more pages with aSmartPdfPageLimitErrorbefore any text is read; an abortedsignalrejects with the signal's reason. - Errors: if the buffer cannot be read as a PDF, the promise rejects with an
Errorwhosecauseis the engine's error. - Compared with PDF.js: a glyph without a Unicode mapping is left out (PDF.js wrote a NUL or a private-use character); text in separately drawn blocks (form XObjects) gets the line breaks and spaces PDF.js left out between them; vertical writing is read as horizontal.
🔍 OCR Scanned PDFs
Add a searchable text layer to a scanned PDF. No browser is needed, so start() is not required, but ocrmypdf, tesseract and Ghostscript must be installed (see Installation):
const searchablePdf = await smartPdf.ocrPdfBytes(scannedPdfBytes, {
languages: ['eng', 'deu'], // tesseract language packs (default: ocrmypdf's default, eng)
rotatePages: true, // rotate pages to the detected text orientation (default: true)
signal: AbortSignal.timeout(120_000), // stop the run
});
const text = await smartPdf.extractTextFromPdfBuffer(searchablePdf);
- Process: the PDF is piped to
ocrmypdf - -and the result read from its stdout. No shell is involved and SmartPDF writes no files; ocrmypdf keeps its working files in the system temp directory (TMPDIR) and removes them. On POSIX, ocrmypdf and the tesseract and Ghostscript processes it starts run in their own, detached process group: a terminal Ctrl+C or the death of your process does not reach them. Stop a run throughsignal; after your process has died, ocrmypdf ends once it tries to write its output to the closed pipe (EPIPE). On Windows, which has no process groups, ocrmypdf runs as a plain child process. The output is ocrmypdf's default, a PDF/A-2b. - Abort: on POSIX an aborted
signalinterrupts the whole process group (SIGINT, so ocrmypdf removes its working files; SIGKILL if it has not exited after 5 seconds) and rejects with the signal's reason once the group has exited. On Windows only the ocrmypdf process is signalled: Node.js terminates it at once (Windows has no SIGINT for it, so it cannot remove its working files), and the tesseract worker processes it started may continue until their current page completes. If a signal cannot be delivered (e.g.EPERM), the call rejects with that error once ocrmypdf has exited. - Errors: an executable that is missing or not executable (
ENOENT,EACCES) rejects with aSmartPdfOcrUnavailableError. A process that cannot be created for another reason, such as exhausted file descriptors (EMFILE) or process limits (EAGAIN), rejects with aSmartPdfOcrErrorwhoseexitCodeandexitSignalarenulland whosecauseis the spawn error with itscode. A failed run rejects with aSmartPdfOcrErrorcarryingexitCode(ocrmypdf's exit codes, e.g.6for a PDF that already has text,2for input that is no PDF),exitSignalandstderrTail, the end of ocrmypdf's stderr. Language codes are validated before the run (RangeError). - Executable:
ocrmypdfPathselects anotherocrmypdfexecutable than the one found inPATH.
🖼️ PDF → Image Conversion
Convert PDF pages to raster images using Puppeteer + PDF.js. Each page becomes a separate image buffer.
Rendering and untrusted PDFs. SmartPDF bundles PDF.js 6 (
pdfjs-dist), which has no eval sink: neither the library nor its worker callsevalornew Function, the path CVE-2024-4367 used to run JavaScript from a malicious font. TheisEvalSupported: falsesetting that protected older PDF.js versions is gone from PDF.js 6 together with that path, so there is nothing to switch off. PDF.js and all of its assets (worker, WebAssembly image decoders, CMaps, standard fonts, ICC profiles) are read frompdfjs-distand served from SmartPDF's own loopback HTTP server, so nothing is fetched from the internet at render time. The render page and every PDF.js asset are served with a Content-Security-Policy withoutunsafe-eval, so the browser blocksevalandnew Functionin the page and in the PDF.js worker;wasm-unsafe-evallets PDF.js compile the WebAssembly that decodes JBIG2 and JPEG 2000 images and PostScript functions.This protects against script execution from a PDF. It does not bound the work a PDF causes: apart from
maxPagesand the 30-second limit on the in-browser render, there is no memory or CPU limit, so a hostile document can still make a render slow or memory-hungry.The image methods need a headless Chromium (via
start()). PDF.js 6 runs in that browser for them; text extraction and page rasters (rasterizePdfPages) run in the PDF engine. The Node.js floor of>=22.13.0is the onepdfjs-distdeclares.
All image methods accept two guard options in addition to scale, maxWidth and maxHeight:
maxPages— refuse a document with more pages than this before any page is rendered (throwsSmartPdfPageLimitError).signal— anAbortSignalthat stops the rendering.
const pages = await smartPdf.convertPDFToPngBytes(untrustedPdf, {
maxPages: 50,
signal: controller.signal,
});
PNG — Lossless Quality
const pngPages = await smartPdf.convertPDFToPngBytes(pdf.buffer, {
scale: SmartPdf.SCALE_HIGH, // 3.0 = ~216 DPI (default)
});
pngPages.forEach((png, i) => {
fs.writeFileSync(`page-${i + 1}.png`, Buffer.from(png));
});
WebP — Modern & Efficient
25–60% smaller than PNG at similar visual quality:
const webpPages = await smartPdf.convertPDFToWebpBytes(pdf.buffer, {
scale: 2.0, // ~144 DPI
quality: 90, // 0–100 (default: 85)
});
JPEG — Progressive Loading
Generates true progressive JPEGs (multi-pass rendering) via sharp:
const jpegPages = await smartPdf.convertPDFToJpegBytes(pdf.buffer, {
scale: SmartPdf.SCALE_HIGH,
quality: 85, // 0–100 (default: 85)
maxWidth: 1920, // optional dimension constraints
maxHeight: 1080,
});
📏 DPI & Scale Reference
All image methods accept a scale parameter. PDF.js renders at 72 DPI by default, so scale is a multiplier:
| Constant | Value | DPI | Use Case |
|---|---|---|---|
SmartPdf.SCALE_SCREEN |
2.0 | ~144 | Web display, thumbnails |
SmartPdf.SCALE_HIGH |
3.0 | ~216 | General purpose (default) |
SmartPdf.SCALE_PRINT |
6.0 | ~432 | Print-quality output |
Or calculate a custom scale:
const scale = SmartPdf.getScaleForDPI(300); // → 4.167
🖼️ Dimension Constraints
All image methods support maxWidth and maxHeight to cap output size while preserving aspect ratio:
// High-res render, but capped at 800×1000 px
const constrained = await smartPdf.convertPDFToWebpBytes(pdf.buffer, {
scale: SmartPdf.SCALE_HIGH,
quality: 90,
maxWidth: 800,
maxHeight: 1000,
});
🧮 Raw Pixels at an Exact DPI
rasterizePdfPages renders each page to raw pixels at the resolution you ask for: what a printer that takes raster pages (PWG Raster, Apple URF) needs, or anything else that works on a page's pixels. A page of w × h points becomes round(w · dpi / 72) × round(h · dpi / 72) pixels as the page shows (its /Rotate applied), in RGB (three bytes a pixel) or gray (one byte, luma), row by row from the top left without padding. Transparent parts are white.
The PDF engine (hayro, Rust) renders the pages in its own process, so no browser runs, the instance need not be started and your event loop does not wait while a page renders; JBIG2 and JPEG 2000 images are decoded by hayro's own decoders. Pass intent: 'print' for pages that go to a printer: annotations are then drawn as they print (one marked not to be printed is left out, one marked not to be viewed but printed is drawn), as PDF.js chose them. Against PDF.js on @napi-rs/canvas, which rendered them before, the first pages of 80 documents at 72 dpi differed by 0.24 gray levels on average (1.2 on the page that differed most), and 0.09 % of their pixels by more than 32 levels.
The pages come one at a time as they render, so a loop that handles each page before it asks for the next holds one page's pixels at a time (an A4 page at 300 dpi is 2480 × 3508 pixels, 26 MB in RGB). Leaving the loop early ends the rendering. maxPages refuses a longer document before any page renders, a page whose raster would be larger than 32,767 pixels a side or 268,435,456 in all (MAX_RASTER_SIDE, MAX_RASTER_AREA) is refused, and an aborted signal stops the work with its reason.
for await (const page of smartPdf.rasterizePdfPages(pdf.buffer, { dpi: 300, colorSpace: 'gray' })) {
// page.pageNumber, page.pageCount, page.width, page.height, page.dpi, page.colorSpace
// page.pixels: Uint8Array of width × height bytes (gray) or width × height × 3 (rgb)
await sendToPrinter(page);
}
📊 Format Comparison
| Format | Typical Size vs PNG | Lossy? | Transparency | Progressive | Best For |
|---|---|---|---|---|---|
| PNG | baseline | No | ✅ | — | Screenshots, diagrams, text-heavy docs |
| WebP | 40–75% | Yes | ✅ | — | Modern web apps, thumbnails |
| JPEG | 50–70% | Yes | ❌ | ✅ | Photos, complex graphics, email |
⚡ Parallel Processing
Process multiple URLs concurrently with separate instances:
const urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3',
];
// Spin up parallel instances
const instances = await Promise.all(
urls.map(() => SmartPdf.create())
);
await Promise.all(instances.map(i => i.start()));
// Generate in parallel
const pdfs = await Promise.all(
urls.map((url, i) => instances[i].getFullWebsiteAsSinglePdf(url))
);
// Merge all results
const merged = await instances[0].mergePdfs(pdfs.map(p => p.buffer));
fs.writeFileSync('all-pages.pdf', merged);
// Clean up
await Promise.all(instances.map(i => i.stop()));
📝 Full API Reference
SmartPdf Class
Static Properties
| Property | Type | Value | Description |
|---|---|---|---|
SCALE_SCREEN |
number |
2.0 |
~144 DPI scale factor |
SCALE_HIGH |
number |
3.0 |
~216 DPI scale factor (default) |
SCALE_PRINT |
number |
6.0 |
~432 DPI scale factor |
Static Methods
| Method | Returns | Description |
|---|---|---|
create(options?) |
Promise<SmartPdf> |
Factory method to create an instance |
getScaleForDPI(dpi) |
number |
Converts a DPI value to a scale factor (dpi / 72) |
Instance Properties
| Property | Type | Description |
|---|---|---|
serverPort |
number |
The port the internal HTTP server is listening on |
Instance Methods
| Method | Returns | Description |
|---|---|---|
start(browser?) |
Promise<void> |
Starts internal server + browser. Optionally accepts an existing Puppeteer Browser. |
stop() |
Promise<void> |
Shuts down server and browser (unless external browser was provided). |
getA4PdfResultForHtmlString(html, options?) |
Promise<IPdf> |
Renders HTML at 794×1122 viewport → A4 PDF; { network: 'blocked' } reaches nothing beyond the string; { readySelector, readyTimeoutMs? } prints once the page's scripts make the selector match |
getPdfResultForWebsite(url) |
Promise<IPdf> |
Captures website at 1980×1200 viewport → PDF |
getFullWebsiteAsSinglePdf(url) |
Promise<IPdf> |
Captures full scrollable page at 1920px wide → single-page PDF |
mergePdfs(buffers) |
Promise<Uint8Array> |
Merges an array of PDF Uint8Array buffers |
assemblePdf(pages) |
Promise<Uint8Array> |
Builds one PDF from pages of PDFs ({ kind: 'pdfPage', pdf, pageNumber, turn? }) and pictures ({ kind: 'image', image, mimeType, dpi?, turn? }), in order |
getPdfPageCount(buffer) |
Promise<number> |
How many pages a PDF has, without rendering it |
readFileToPdfObject(path) |
Promise<IPdf> |
Reads a PDF file from disk into an IPdf object |
extractTextFromPdfBuffer(buffer, opts?) |
Promise<string> |
Extracts the text of every page (options: maxPages, signal) |
extractTextPagesFromPdfBuffer(buffer, opts?) |
Promise<string[]> |
The text of each page on its own, one entry a page (same options) |
ocrPdfBytes(buffer, opts?) |
Promise<Uint8Array> |
Adds a text layer to a scanned PDF with ocrmypdf (options: languages, rotatePages, signal, ocrmypdfPath) |
convertPDFToPngBytes(buffer, opts?) |
Promise<Uint8Array[]> |
Converts each PDF page to a PNG buffer |
convertPDFToWebpBytes(buffer, opts?) |
Promise<Uint8Array[]> |
Converts each PDF page to a WebP buffer |
convertPDFToJpegBytes(buffer, opts?) |
Promise<Uint8Array[]> |
Converts each PDF page to a progressive JPEG buffer |
rasterizePdfPages(buffer, opts) |
AsyncGenerator<IPdfPageRaster> |
Renders each page to raw pixels at an exact DPI, one page at a time, in the PDF engine without a browser (options: dpi, colorSpace 'rgb'/'gray', intent 'display'/'print', maxPages, signal) |
Image Conversion Options
{
scale?: number; // DPI multiplier (default: 3.0)
quality?: number; // 0–100, WebP/JPEG only (default: 85)
maxWidth?: number; // Max output width in pixels
maxHeight?: number; // Max output height in pixels
maxPages?: number; // Refuse longer documents before rendering (SmartPdfPageLimitError)
signal?: AbortSignal; // Stops the rendering
}
ISmartPdfOptions Interface
{
port?: number; // Use a specific port
portRangeStart?: number; // Auto-allocation range start (default: 20000)
portRangeEnd?: number; // Auto-allocation range end (default: 30000)
}
Migration from @push.rocks/smartocr
@push.rocks/smartocr is deprecated; its OCR now ships with SmartPDF.
@push.rocks/smartocr |
@push.rocks/smartpdf |
|---|---|
const ocr = await SmartOcr.createAndInit() |
const smartPdf = await SmartPdf.create() (no start() needed for OCR) |
await ocr.processPdfBuffer(pdfBuffer) → Buffer |
await smartPdf.ocrPdfBytes(pdfBytes) → Uint8Array (wrap with Buffer.from() where a Buffer is needed) |
always --rotate-pages |
rotatePages option, default true |
| — | languages, signal and ocrmypdfPath options |
ran ocrmypdf through a bash command line with temp files in the package's .nogit folder |
runs ocrmypdf without a shell over stdin/stdout |
init() (and so createAndInit()) rejected with not found: ocrmypdf when it was missing; a failed run surfaced as a file-read error |
SmartPdfOcrUnavailableError / SmartPdfOcrError with exit code and stderr tail |
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.