jkunz 59c501ce36
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 2s
Default (tags) / metadata (push) Skipped
v7.0.1
2026-11-01 19:41:15 +00:00
2026-11-01 19:41:15 +00:00
2026-11-01 19:41:15 +00:00
2026-11-01 19:41:15 +00:00

@push.rocks/smartpdf 📄✨

Transform HTML, websites, and PDFs into beautiful documents and images with just a few lines of code.

npm version TypeScript License: MIT

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/smartrust drives. The package ships it for Linux on amd64 and arm64 (static musl builds that need no system libraries). On other platforms these methods need a smartpdf-bin built from rust/ (cargo build --release) and named by SMARTPDF_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-dist declares @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:

  1. Your HTML is registered internally and served at http://localhost:{port}/{id}
  2. Puppeteer navigates to that URL, waits for the page to fully render, and captures a PDF
  3. A header-based security check ensures only the correct content is captured
  4. 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 as data: 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 as PDF_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): what extractTextFromPdfBuffer joins. 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: maxPages refuses a document with more pages with a SmartPdfPageLimitError before any text is read; an aborted signal rejects with the signal's reason.
  • Errors: if the buffer cannot be read as a PDF, the promise rejects with an Error whose cause is 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 through signal; 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 signal interrupts 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 a SmartPdfOcrUnavailableError. A process that cannot be created for another reason, such as exhausted file descriptors (EMFILE) or process limits (EAGAIN), rejects with a SmartPdfOcrError whose exitCode and exitSignal are null and whose cause is the spawn error with its code. A failed run rejects with a SmartPdfOcrError carrying exitCode (ocrmypdf's exit codes, e.g. 6 for a PDF that already has text, 2 for input that is no PDF), exitSignal and stderrTail, the end of ocrmypdf's stderr. Language codes are validated before the run (RangeError).
  • Executable: ocrmypdfPath selects another ocrmypdf executable than the one found in PATH.

🖼️ 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 calls eval or new Function, the path CVE-2024-4367 used to run JavaScript from a malicious font. The isEvalSupported: false setting 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 from pdfjs-dist and 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 without unsafe-eval, so the browser blocks eval and new Function in the page and in the PDF.js worker; wasm-unsafe-eval lets 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 maxPages and 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.0 is the one pdfjs-dist declares.

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 (throws SmartPdfPageLimitError).
  • signal — an AbortSignal that 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

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.

S
Description
A library for creating PDFs dynamically from HTML or websites with additional features like merging PDFs.
Readme MIT
5.7 MiB
0 Stars 3 Watchers 0 Forks
Languages
HTML 59.6%
TypeScript 27.7%
Rust 12.7%