Render, generate, edit, and extract PDF content

This guide shows developers how to perform core PDF operations with PDFluent. It covers rendering PDFs to images, editing documents, and processing annotations.

Render PDFs to images, anywhere.

Convert PDF pages to PNG, JPEG, or WebP with sub-pixel accuracy. Runs server-side, in Lambda, or via WASM.
rust
use pdfluent::{PdfDocument, ImageFormat};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = PdfDocument::open("document.pdf")?;

    let png = doc.render_page(0, 150, ImageFormat::Png)?;
    std::fs::write("page-1.png", png)?;
    Ok(())
}
What image formats does PDFluent render to?
PNG, JPEG, and WebP. PNG is lossless and the default. JPEG accepts a quality parameter from 1 to 100. WebP supports both lossy and lossless modes.
Does rendering require installed fonts?
No. PDFluent uses fonts embedded in the PDF. For pages that reference system fonts not embedded in the document, PDFluent falls back to a bundled font set so rendering never fails due to missing fonts.
How does render quality compare to Ghostscript?
For standard PDF content, output is visually equivalent at the same DPI. PDFluent handles transparency, blend modes, and gradients natively. Complex PostScript streams are not supported because PDFluent is a PDF renderer, not a PostScript interpreter.
Can I render password-protected PDFs?
Yes. Pass the user password to PdfDocument::open_with(path, OpenOptions::new().with_password(...)) before calling render_page. Owner-password-only files (where the user password is empty) open without a password.

Generate PDF thumbnails at scale.

Convert first pages or any pages to JPEG/PNG thumbnails. Batch process document libraries without a display server.
rust
use pdfluent::{PdfDocument, ImageFormat};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = PdfDocument::open("document.pdf")?;

    for i in 0..doc.page_count() {
        let png = doc.render_page(i, 72, ImageFormat::Png)?;
        std::fs::write(format!("thumb-{}.png", i + 1), png)?;
    }
    Ok(())
}
How fast is thumbnail generation?
Most of the time in thumbnail generation is spent on PDF parsing and image encoding. For JPEG output, enabling the turbo-jpeg feature flag reduces encoding time.
Can I thumbnail a specific area of a page?
Yes. Pass a crop rectangle to ThumbnailOptions. The crop is specified in PDF user space coordinates. Only the cropped area is rendered and encoded.
What happens with very long or very wide pages?
When you set only the width, the height is calculated from the page aspect ratio. A page that is 8.5x14 inches (legal) will produce a taller thumbnail than a letter-size page at the same width. Set both width and height with fit mode to cap both dimensions.
Does thumbnail generation support encrypted PDFs?
Yes. Open the document with PdfDocument::open_with(path, OpenOptions::new().with_password(...)) before calling thumbnail(). If the PDF uses an empty user password (common for permission-only encryption), PdfDocument::open() opens it directly.

Run PDF processing in the browser.

PDFluent compiles to WASM. Process PDFs client-side without uploading files to a server.
rust
const bytes = new Uint8Array(await file.arrayBuffer());
const doc = PdfDoc.open(bytes);

// renderPage returns raw RGBA with an 8-byte header: width, height, then pixels.
const raw = doc.renderPage(0, 1.5);
const view = new DataView(raw.buffer);
const w = view.getUint32(0, true);
const h = view.getUint32(4, true);
const imageData = new ImageData(new Uint8ClampedArray(raw.slice(8)), w, h);
ctx.putImageData(imageData, 0, 0);
How large is the WASM binary?
The published @pdfluent/sdk-wasm@1.0.0 .wasm is 15.3 MB unpacked, 3.8 MB Brotli, 5.5 MB gzip [B07, 2026-09-16]. It is cached by the browser after the first visit.
Are all features available in the WASM build?
Rendering, metadata, annotations, text extraction, merging, splitting, and encryption are all available. Features that depend on the local filesystem (saving directly to disk) are not available in the browser build. Use saveBytes() to get the output as a Uint8Array and download it from the browser.
Can I use PDFluent WASM in a Web Worker?
Yes. Load the WASM module in a Web Worker to avoid blocking the main thread. PDFluent WASM is fully compatible with the Worker global scope. Structured clone transfers work for passing Uint8Arrays back to the main thread.
How does performance compare to the native Rust build?
WASM runs slower than native for CPU-bound operations like rendering. For I/O-bound tasks like metadata reads, the difference is negligible. Rendering time for a single page depends on page complexity.

Edit existing PDFs in Rust.

Replace text, stamp, fill forms and update metadata on documents that already exist.
rust
use pdfluent::{PdfDocument, ReplaceOptions, TextQuery, WatermarkOptions};

fn approve(input: &[u8]) -> pdfluent::Result<Vec<u8>> {
    let mut doc = PdfDocument::from_bytes(input)?;

    // Rewrite text that is already on the page, in its own font
    doc.replace_text(
        TextQuery::exact("Status: pending"),
        "Status: approved",
        ReplaceOptions::default(),
    )?;

    // Stamp every page
    doc.add_watermark(
        "APPROVED",
        WatermarkOptions::centered().opacity(0.3),
    )?;

    // Fill a field and make the value permanent
    doc.form_mut().set_text("approver", "Finance Team")?;
    doc.flatten_forms()?;

    // Update document metadata
    doc.metadata_mut()
        .set_author("Finance Team")
        .set_keywords(&["approved", "processed", "2026"])
        .commit()?;

    doc.to_bytes()
}
How does PDFluent compare to Adobe Acrobat SDK or iText for editing?
Adobe Acrobat SDK requires a licensed Acrobat installation; iText requires a Java runtime and a commercial licence for closed-source use. PDFluent is a pure Rust crate — one dependency in Cargo.toml, no additional runtime, no per-server licensing. If you're already in the Rust ecosystem or targeting serverless, PDFluent is the simpler path.
Can I edit PDFs that contain existing text and images?
Yes, and that is the only kind PDFluent edits. It opens the file, parses its content streams, and rewrites the parts you name. What it does not do is author new content: there is no API for drawing text or adding a page, so a document has to exist before PDFluent can work on it.
Can I add new text at an arbitrary position on the page?
No. PDFluent has no drawing API -- no add_text, no font placement, no page creation. It replaces text that is already there and it can stamp a watermark, but laying out new content is a job for a document generator. If you need to produce a PDF from data, generate it with a tool built for that and use PDFluent on the result.
Does it support AcroForm (interactive form) editing?
Yes. PDFluent supports reading and writing AcroForm fields including text inputs, checkboxes, radio buttons, and select lists. You can also flatten a filled form so the values become static page content that cannot be changed by the reader.
Can I use PDFluent for PDF editing in a serverless environment?
Yes. PDFluent compiles to wasm32-unknown-unknown for Cloudflare Workers and to a static binary for AWS Lambda and similar FaaS platforms. Editing a typical form PDF stays within Lambda memory limits.

Add watermarks and stamps to PDFs.

Apply text watermarks, image overlays, and stamps to single pages or entire documents. Batch watermark with per-document variable data.
rust
use pdfluent::{PdfDocument, WatermarkOptions};

fn main() -> pdfluent::Result<()> {
    let mut doc = PdfDocument::open("document.pdf")?;

    let opts = WatermarkOptions::centered()
        .font_size(64.0)
        .rotated(45.0)
        .opacity(0.15)
        .color(0.6, 0.0, 0.0);

    doc.add_watermark("CONFIDENTIAL", opts)?;
    doc.save("watermarked.pdf")?;
    Ok(())
}
Is the watermark part of the content stream or an annotation?
By default, watermarks are written directly into the page content stream. This makes them non-removable by standard annotation-removal tools. Optionally, you can write the watermark as a form XObject so it can be targeted for removal later using the watermark removal API.
Can I use a custom font for the watermark text?
Yes. Pass the path to any TrueType or OpenType font file via font_path() on the TextWatermark builder. PDFluent embeds a subset of the font in the output document.
What opacity value produces a visible but unobtrusive watermark?
Low opacity values work well for diagonal text on typical white-background documents. For colored or image-heavy pages, raise the opacity so the watermark stays readable.
Does watermarking work on scanned PDFs?
Yes. Scanned PDFs contain page images rather than text. PDFluent adds the watermark as a content layer on top of the page image, which renders correctly in all viewers.

Text extraction. Done right.

PDF text extraction. Preserve layout, reading order, and font metadata.
rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("document.pdf")?;

    // Plain text
    println!("{}", doc.extract_text()?);

    // Positioned text blocks: text + bounding box + page
    for block in doc.text_with_layout()? {
        println!("p{} {:?} {}", block.page, block.bbox, block.text);
    }
    Ok(())
}
How do I check text extraction on my own documents?
PDFluent's text extraction handles the common cases. The failures we see are edge cases involving complex font encodings or non-standard content stream structures. Each failure is reported with exact coordinates for debugging.
How does PDFluent handle multi-column layouts?
PDFluent builds a spatial model of each page and sorts content by reading order — not object order. Multi-column layouts, sidebars, and footnotes are detected and sorted correctly. Table structures are recognized and preserved as structured data.
Can I get coordinates for each text block?
Yes. Every text block includes x, y, width, and height coordinates. This lets you map extracted text back to the original PDF for highlighting, annotation, or precise text replacement.
Can I batch-extract text from thousands of documents?
Yes. PDFluent supports parallel batch processing with configurable worker threads. Each document gets a structured JSON output with page structure, font metadata, and coordinates.