Merge, split, edit, and inspect PDFs

This guide covers common PDF manipulation tasks using the PDFluent SDK. It is for Rust developers who need to process PDF files programmatically.

Add PDFluent to your project

Add the pdfluent crate to your Cargo.toml. No system libraries are required.

toml
[dependencies]
pdfluent = "1.0.0-beta.18"

Merge PDFs in Rust

Combine multiple PDF files into one document. PDFluent preserves bookmarks across all input files.

rust
use pdfluent::prelude::*;

fn main() -> Result<()> {
    let merged = PdfMerger::new()
        .add(PdfDocument::open("part1.pdf")?)
        .add(PdfDocument::open("part2.pdf")?)
        .add(PdfDocument::open("part3.pdf")?)
        .build()?;

    merged.save("combined.pdf")?;

    println!("Merged {} pages into combined.pdf", merged.page_count());
    Ok(())
}
  1. Open each input document

    Open every source PDF with PdfDocument::open. The merger takes full PdfDocument values by move, so consume each input once.

    rust
    use pdfluent::prelude::*;
    
    let a = PdfDocument::open("invoice_jan.pdf")?;
    let b = PdfDocument::open("invoice_feb.pdf")?;
    let c = PdfDocument::open("invoice_mar.pdf")?;
  2. Build a PdfMerger and add inputs

    Create a PdfMerger, chain .add() for each document. Inputs are appended in the order they are added.

    rust
    let merger = PdfMerger::new()
        .add(a)
        .add(b)
        .add(c);
  3. Configure bookmark handling

    Choose a BookmarkMergeStrategy. Concat (the default) groups each source's bookmarks under a top-level entry; FlattenAll sequences them; Discard drops all bookmarks. In 1.0 Concat has dedicated treatment; FlattenAll and Discard fall back to the underlying concatenation.

    rust
    let merger = merger
        .with_bookmarks(BookmarkMergeStrategy::Concat)
        .with_page_labels(true); // 1.0: accepted but currently a no-op
  4. Build and save

    Call .build() to produce a merged PdfDocument, then save or serialise it. build() is the terminating step and consumes the merger.

    rust
    let merged = merger.build()?;
    merged.save("annual_report.pdf")?;
    
    println!("Total pages: {}", merged.page_count());
  • Named destinations from each input file are remapped so they remain valid in the merged document.
  • Page labels from source files are treated on a best-effort basis in 1.0; full preservation lands in 1.1.
  • Encrypted PDFs must be decrypted before merging — open them with OpenOptions::new().with_password("...") or call doc.decrypt("...") first.
  • The merger processes inputs in the order they were added. Page numbering in the output starts at 1 and increases sequentially.
  • Input bytes: open from memory with PdfDocument::from_bytes(&bytes) before adding to the merger — there is no separate add_bytes entry point.

Merge PDFs and generate a table of contents in Rust

Combine multiple PDF files into one and insert a generated table of contents page with clickable bookmark links.

rust
use pdfluent::{PdfDocument, PdfMerger, BookmarkMergeStrategy};

fn main() -> pdfluent::Result<()> {
    let merged = PdfMerger::new()
        .add(PdfDocument::open("intro.pdf")?)
        .add(PdfDocument::open("body.pdf")?)
        .with_bookmarks(BookmarkMergeStrategy::Concat)
        .with_page_labels(true)
        .build()?;
    merged.save("merged.pdf")?;
    Ok(())
}
  1. Prepare MergeInput entries

    Each MergeInput specifies a source file and an optional title used for the TOC entry and bookmark.

    rust
    use pdfluent::{PdfDocument, PdfMerger, BookmarkMergeStrategy};
    
    let merger = PdfMerger::new()
        .add(PdfDocument::open("section1.pdf")?)
        .add(PdfDocument::open("section2.pdf")?)
        .add(PdfDocument::open("appendix.pdf")?);
  2. Configure merge options

    Enable TOC generation and optionally configure the TOC page style, font, and bookmark depth.

    rust
    // Concatenate each source's bookmarks under a top-level entry (navigation TOC).
    // Page labels preserve each section's original numbering.
    let merger = merger
        .with_bookmarks(BookmarkMergeStrategy::Concat)
        .with_page_labels(true);
  3. Merge and get the document

    Document::merge_with_options returns a new Document. The TOC page is inserted at the position specified in the options.

    rust
    let merged = merger.build()?;
  4. Inspect the generated outlines

    The merge operation adds bookmark entries that match the TOC. Verify they are present.

    rust
    for bookmark in merged.outlines()? {
        println!("'{}' -> page {:?}", bookmark.title, bookmark.page);
    }
  5. Save the merged document

    Write the final merged file.

    rust
    merged.save("merged_with_toc.pdf")?;
  • The TOC page is generated using the page dimensions of the first page in the merge list. Override with MergeOptions::toc_page_size(PageSize::A4).
  • Existing bookmarks from each source document are preserved and nested under the top-level chapter bookmark.
  • If a source document is encrypted, decrypt it before passing to MergeInput or pass the password with MergeInput::with_password(pw).
  • Named destinations from each source document are re-scoped with a per-document prefix to avoid collisions.

Split a PDF by page range in Rust

Extract one or more page ranges from a PDF and write each range to a separate file. Useful for splitting chapters, invoices, or reports.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("report.pdf")?;
    for (i, page) in doc.split_pages()?.into_iter().enumerate() {
        page.save(format!("page-{}.pdf", i + 1))?;
    }
    Ok(())
}
  1. Open the source document

    Load the PDF you want to split. PDFluent reads the file lazily, so opening a 500-page document uses minimal memory until pages are accessed.

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("quarterly_report.pdf")?;
    println!("Source has {} pages", doc.page_count());
  2. Define page ranges

    Create PageRange values for each segment. Pages are 1-indexed. Ranges can overlap if you need the same page in multiple output files.

    rust
    // Ranges to extract (0-based, end-exclusive)
    let ranges = [0..5usize, 5..18, 18..22];
  3. Split and write to separate files

    Pass the ranges to split_by_ranges(). Use {n} in the output pattern for the range index, or provide a Vec of explicit output paths.

    rust
    for (i, range) in ranges.iter().enumerate() {
        doc.extract_pages(range.clone())?.save(format!("segment_{}.pdf", i + 1))?;
    }
    // Produces: segment_1.pdf, segment_2.pdf, segment_3.pdf
  4. Use explicit output names

    When you need specific filenames, pass a slice of paths with the same length as the ranges slice.

    rust
    let names = ["cover.pdf", "body.pdf", "appendix.pdf"];
    for (range, name) in ranges.iter().zip(names) {
        doc.extract_pages(range.clone())?.save(name)?;
    }
  5. Split into in-memory buffers

    If you need to serve the split files over HTTP without touching disk, use to_bytes_vec() instead.

    rust
    for (i, range) in ranges.iter().enumerate() {
        let bytes = doc.extract_pages(range.clone())?.to_bytes()?;
        println!("Segment {}: {} bytes", i + 1, bytes.len());
    }
  • Page indices are 1-based. Passing 0 returns an error.
  • Ranges that extend past the last page are clamped to the last page automatically.
  • Bookmarks pointing to pages outside a range are dropped in that output file.
  • AcroForm fields on pages that fall within a range are included in the corresponding output file.

Split a PDF at each top-level bookmark

Use the PDF outline to split a document into sections automatically. Each top-level bookmark becomes its own output file.

rust
use pdfluent::PdfDocument;

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

    let results = doc
        .split_by_top_level_bookmarks()
        .write_files("{title}.pdf")?;

    for r in &results {
        println!("{} -> {} pages", r.filename, r.page_count);
    }
    Ok(())
}
  1. Open the PDF and inspect its outline

    Check that the document has top-level bookmarks before splitting. PDFluent exposes the full outline tree via outline().

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("manual.pdf")?;
    let outline = doc.outline()?;
    
    println!("Top-level sections: {}", outline.len());
    for item in &outline {
        println!("  {} -> page {}", item.title, item.destination_page);
    }
  2. Split at top-level bookmarks

    split_by_top_level_bookmarks() computes the page range for each bookmark automatically. The range ends where the next bookmark starts.

    rust
    let splitter = doc.split_by_top_level_bookmarks();
  3. Write output files using the bookmark title as filename

    Use {title} in the pattern to name each file after its bookmark. PDFluent sanitises the title to produce a valid filename.

    rust
    splitter.write_files("{title}.pdf")?;
    // "Introduction.pdf", "Chapter 1.pdf", "Chapter 2.pdf", ...
  4. Split by a specific outline depth

    To split at second-level bookmarks instead of the top level, set the depth parameter.

    rust
    use pdfluent::SplitDepth;
    
    doc.split_by_bookmarks(SplitDepth::Level(2))
        .write_files("section_{n}.pdf")?;
  5. Collect results for further processing

    If you need the split data in memory, use to_vec() to get a Vec of SplitSegment values without writing to disk.

    rust
    let segments = doc
        .split_by_top_level_bookmarks()
        .to_vec()?;
    
    for seg in segments {
        println!("{}: {} bytes", seg.title, seg.data.len());
        // upload seg.data to S3, etc.
    }
  • If the last bookmark has no following bookmark, its range extends to the last page of the document.
  • Bookmarks that point to the same page as the next bookmark produce a zero-page segment. PDFluent skips these by default.
  • Child bookmarks are included in the parent segment, not extracted separately, unless you use SplitDepth::Level(n).

Extract specific pages from a PDF in Rust

Pick individual pages or non-contiguous sets and write them to a new PDF. Works with page numbers, page labels, or a custom predicate.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("report.pdf")?;
    let subset = doc.extract_pages(0..3)?;
    subset.save("first-three.pdf")?;
    Ok(())
}
  1. Open the source PDF

    Open the document you want to extract pages from. Page count is available immediately after opening.

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("source.pdf")?;
    println!("{} pages total", doc.page_count());
  2. Extract by page numbers

    Pass a slice of 1-based page numbers. Pages appear in the output in the order given, so you can reorder them freely.

    rust
    // Extract a contiguous page range (0-based, end-exclusive): pages 2-4
    let extracted = doc.extract_pages(1..4)?;
    extracted.save("pages_2_to_4.pdf")?;
  3. Extract by page label

    If the PDF uses custom page labels such as "i", "ii", "A-1", pass label strings instead of integers.

    rust
    // Split into single-page documents
    for (i, page) in doc.split_pages()?.into_iter().enumerate() {
        page.save(format!("page_{}.pdf", i + 1))?;
    }
  4. Extract with a filter predicate

    Use extract_pages_where() to filter programmatically. The closure receives a PageInfo struct with page number, label, width, height, and rotation.

    rust
    // Extract a range and read the bytes (e.g. to send over HTTP)
    let bytes = doc.extract_pages(0..3)?.to_bytes()?;
    println!("Extracted PDF is {} bytes", bytes.len());
  5. Save or return as bytes

    Call save() to write to disk, or to_bytes() to get the PDF data as a Vec<u8> for streaming or further processing.

    rust
    // Extract the final two pages by range and save
    let n = doc.page_count();
    doc.extract_pages(n.saturating_sub(2)..n)?.save("last_pages.pdf")?;
  • Pages are extracted in the order of the input slice. Duplicates are allowed and produce repeated pages.
  • Annotations and form fields on extracted pages are included.
  • If a page number is out of range, extract_pages returns an Err immediately before writing any output.

Delete pages from a PDF in Rust

Remove one page, a range of pages, or a custom set of page indices from a PDF. Bookmarks and named destinations are updated automatically.

rust
use pdfluent::PdfDocument;

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

    // Remove page 5 (zero-based index 4)
    doc.delete_page(4)?;

    doc.save("report_trimmed.pdf")?;
    println!("Remaining pages: {}", doc.page_count());
    Ok(())
}
  1. Open the PDF

    Load the document you want to modify.

    rust
    use pdfluent::PdfDocument;
    
    let mut doc = PdfDocument::open("draft.pdf")?;
    println!("Before: {} pages", doc.page_count());
  2. Delete a single page

    Call delete_page() with the zero-based index of the page to remove. After deletion, indices of subsequent pages shift down by one.

    rust
    // Remove the last page
    let last = doc.page_count() - 1;
    doc.delete_page(last)?;
    
    // Remove the first page
    doc.delete_page(0)?;
  3. Delete a range of pages

    delete_page_range(start..end) removes all pages from start (inclusive) to end (exclusive). This is faster than looping with delete_page().

    rust
    // Remove pages 3 through 6 (indices 2..6, exclusive end)
    doc.delete_page_range(2..6)?;
    println!("After range delete: {} pages", doc.page_count());
  4. Delete non-contiguous pages and save

    Pass a Vec or slice of indices to delete_pages(). Provide indices in any order; PDFluent sorts and removes them correctly.

    rust
    // Remove pages at indices 1, 4, and 7
    doc.delete_pages(&[1, 4, 7])?;
    
    doc.save("draft_cleaned.pdf")?;
    println!("Final page count: {}", doc.page_count());
  • After deleting pages, all subsequent page indices decrease. Do not cache page indices across delete calls.
  • Bookmarks pointing to deleted pages are removed from the outline automatically.
  • delete_pages() accepts indices in any order. Duplicates are ignored.
  • Deleting all pages from a document is not allowed. At least one page must remain.

Insert blank or existing pages into a PDF in Rust

Insert a blank page, a page from another PDF, or multiple pages at any position in an existing document.

rust
use pdfluent::{PdfDocument, PageSize};

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

    // Insert a blank A4 page after page 2 (at index 2)
    doc.insert_blank_page(2, PageSize::A4)?;

    doc.save("report_with_divider.pdf")?;
    println!("Page inserted. New count: {}", doc.page_count());
    Ok(())
}
  1. Open the target PDF

    Load the document you want to modify.

    rust
    use pdfluent::PdfDocument;
    
    let mut doc = PdfDocument::open("annual_report.pdf")?;
    println!("Current page count: {}", doc.page_count());
  2. Insert a blank page at a position

    insert_blank_page(index, size) inserts a new empty page before the page at that index. Use index = page_count() to append at the end.

    rust
    use pdfluent::PageSize;
    
    // Insert blank A4 page before the third page (index 2)
    doc.insert_blank_page(2, PageSize::A4)?;
    
    // Append a blank Letter page at the end
    doc.insert_blank_page(doc.page_count(), PageSize::Letter)?;
  3. Insert pages from another PDF

    Open a second document and copy pages from it into the target at a specific position.

    rust
    let source = PdfDocument::open("cover_page.pdf")?;
    
    // Insert the first page of source before page 0 (prepend)
    doc.insert_page_from(0, &source, 0)?;
    
    // Insert pages 1-3 from source after the current last page
    for i in 1..=3 {
        let pos = doc.page_count();
        doc.insert_page_from(pos, &source, i)?;
    }
  4. Save the modified document

    Write the result to disk.

    rust
    doc.save("report_expanded.pdf")?;
    println!("New page count: {}", doc.page_count());
  • Insertion indices are zero-based. Inserting at index 0 prepends the page before the current first page.
  • insert_page_from() copies the page content, resources, and annotations from the source document.
  • Bookmarks from the source document are not automatically carried over. Add them manually if needed.
  • PageSize::Custom(width, height) accepts dimensions in points for non-standard page sizes.

Change the page order of a PDF in Rust

Rearrange, reverse, or interleave pages in a PDF document using a page index map.

rust
use pdfluent::PdfDocument;

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

    // Move the last page to the front
    let count = doc.page_count();
    let mut order: Vec<usize> = (0..count).collect();
    order.rotate_right(1);

    doc.reorder_pages(&order)?;
    doc.save("reordered.pdf")?;
    Ok(())
}
  1. Open the PDF

    Load the document and check the page count so you can build the index map.

    rust
    let mut doc = PdfDocument::open("input.pdf")?;
    let count = doc.page_count();
  2. Build a new page order

    Create a Vec<usize> where each element is the zero-based index of the original page you want at that position. The vec must contain every index exactly once.

    rust
    // Reverse all pages
    let order: Vec<usize> = (0..count).rev().collect();
  3. Apply the new order

    Call reorder_pages with the index slice. PDFluent rewrites the /Pages tree; no content streams are copied or duplicated.

    rust
    doc.reorder_pages(&order)?;
  4. Move a specific page to a new position

    Remove a page index from its current position and insert it at the target position.

    rust
    let mut order: Vec<usize> = (0..count).collect();
    // Move page at index 4 to position 1
    let page = order.remove(4);
    order.insert(1, page);
    doc.reorder_pages(&order)?;
  5. Save the result

    Write the reordered document to a file.

    rust
    doc.save("reordered.pdf")?;
  • The order slice must have exactly page_count() elements. Missing or duplicate indices return an error.
  • Bookmarks (outlines) that reference page destinations are updated automatically when page order changes.
  • Named destinations are also remapped so internal links continue to resolve correctly.
  • If you need to interleave two documents, use Document::merge first, then reorder.

Rotate one or all pages of a PDF in Rust

Set page rotation to 90, 180, or 270 degrees. Rotate a single page, a range, or the whole document in a few lines of Rust.

rust
use pdfluent::{PdfDocument, Rotation};

fn main() -> pdfluent::Result<()> {
    let mut doc = PdfDocument::open("scan.pdf")?;
    for page in 0..doc.page_count() {
        doc.rotate_page(page, Rotation::Clockwise90)?;
    }
    doc.save("rotated.pdf")?;
    Ok(())
}
  1. Open the PDF

    Load the file you want to rotate.

    rust
    use pdfluent::PdfDocument;
    
    let mut doc = PdfDocument::open("scanned_contract.pdf")?;
  2. Rotate a single page

    Get a mutable reference to the page and call set_rotation(). The Rotation enum has variants for 0, 90, 180, and 270 degrees.

    rust
    use pdfluent::Rotation;
    
    // Rotate page 3 (index 2) 90 degrees clockwise
    doc.rotate_page(2, Rotation::Clockwise90)?;
    
    // Rotate page 4 upside-down
    doc.rotate_page(3, Rotation::Clockwise180)?;
  3. Rotate a page range

    Loop over a range of page indices to rotate a contiguous section.

    rust
    // Rotate pages 5 through 8 (indices 4-7)
    for i in 4..8 {
        doc.rotate_page(i, Rotation::Clockwise270)?;
    }
  4. Rotate all pages and save

    Iterate over all pages and apply the same rotation, then write the output.

    rust
    let count = doc.page_count();
    for i in 0..count {
        doc.rotate_page(i, Rotation::Clockwise90)?;
    }
    
    doc.save("document_rotated.pdf")?;
    println!("Rotated {} pages", count);
  • set_rotation() sets the /Rotate entry in the page dictionary. This is the standard PDF rotation mechanism.
  • Rotation is additive if you call it multiple times on the same page. Use Rotation::None to reset to zero degrees.
  • Page content streams are not modified. The rotation is stored as metadata and applied by the PDF viewer.
  • To get the current rotation before changing it, call page.rotation() which returns a Rotation value.

Crop a PDF page by setting the crop box in Rust

Set the CropBox on any page to define the visible area without removing content from the file.

rust
use pdfluent::{PdfDocument, Rect};

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

    // Crop the first page to a 400x500 pt region starting at (50, 100)
    let crop_box = Rect::new(50.0, 100.0, 450.0, 600.0);
    doc.page_mut(0)?.set_crop_box(crop_box);

    doc.save("cropped.pdf")?;
    Ok(())
}
  1. Open the PDF document

    Load the file into a mutable Document. The document must be opened with write access so you can modify page boxes.

    rust
    let mut doc = PdfDocument::open("input.pdf")?;
  2. Understand coordinate space

    PDF coordinates start at the bottom-left corner of the page. A Rect is defined as (x_min, y_min, x_max, y_max) in points (1 pt = 1/72 inch). A standard A4 page is 595 x 842 pt.

    rust
    // A4 page: bottom-left (0,0), top-right (595, 842)
    // This rect keeps a 20 pt margin on all sides:
    let rect = Rect::new(20.0, 20.0, 575.0, 822.0);
  3. Set the CropBox on the target page

    The CropBox controls what viewers display. Content outside the CropBox is hidden but not deleted. You can remove the CropBox later to restore the full page.

    rust
    let crop_box = Rect::new(50.0, 100.0, 450.0, 600.0);
    doc.page_mut(0)?.set_crop_box(crop_box);
  4. Apply the same crop to multiple pages

    Iterate over all pages and set the same CropBox, or compute a per-page crop based on the MediaBox dimensions.

    rust
    let page_count = doc.page_count();
    for i in 0..page_count {
        let page = doc.page_mut(i)?;
        let media = page.media_box();
        // Remove a 30 pt border on all sides
        let crop = Rect::new(
            media.x_min + 30.0,
            media.y_min + 30.0,
            media.x_max - 30.0,
            media.y_max - 30.0,
        );
        page.set_crop_box(crop);
    }
  5. Save the modified document

    Write the result to a new file. The original content is preserved inside the file; only the CropBox annotation changes.

    rust
    doc.save("cropped.pdf")?;
  • Setting a CropBox does not delete page content. Viewers that ignore the CropBox will still show the full page.
  • The CropBox must be contained within the MediaBox. Passing a larger rect is technically invalid and may cause viewer warnings.
  • BleedBox, TrimBox, and ArtBox are separate boxes that can coexist with the CropBox. Use set_trim_box() for print workflows.
  • To remove a crop, call page.clear_crop_box() to reset the visible area to the full MediaBox.

Change the page size of a PDF in Rust

Rescale or replace the MediaBox on each page to change the physical dimensions of a PDF.

rust
use pdfluent::{PdfDocument, PageSize, Rect};

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

    let page_count = doc.page_count();
    for i in 0..page_count {
        doc.page_mut(i)?.resize(PageSize::A4, true)?;
    }

    doc.save("resized.pdf")?;
    Ok(())
}
  1. Open the document

    Load the PDF into a mutable Document handle.

    rust
    let mut doc = PdfDocument::open("input.pdf")?;
  2. Choose a target page size

    PDFluent provides a PageSize enum with standard ISO and North American sizes. You can also supply a custom Rect in points.

    rust
    use pdfluent::PageSize;
    
    // Standard sizes
    let a4 = PageSize::A4;           // 595 x 842 pt
    let letter = PageSize::Letter;   // 612 x 792 pt
    let a3 = PageSize::A3;           // 842 x 1191 pt
    
    // Custom size: 4 x 6 inches
    let custom = PageSize::Custom(Rect::new(0.0, 0.0, 288.0, 432.0));
  3. Resize a page and scale the content

    The second parameter controls whether the content stream is scaled proportionally to fill the new MediaBox. Pass false to keep content at original scale (it may clip or leave whitespace).

    rust
    doc.page_mut(0)?.resize(PageSize::A4, true)?;
  4. Resize all pages in a loop

    Iterate over every page index and apply the same resize operation.

    rust
    let count = doc.page_count();
    for i in 0..count {
        doc.page_mut(i)?.resize(PageSize::Letter, true)?;
    }
  5. Save the output

    Write the resized document to disk.

    rust
    doc.save("resized.pdf")?;
  • Scaling content proportionally may leave margins if the aspect ratio of the original and target sizes differ.
  • Resize updates the MediaBox. If a CropBox was set, it is cleared to match the new MediaBox.
  • Rotation is preserved. A landscape page stays landscape after resize unless you call page.set_rotation(0) first.
  • For high-quality output, scale at a 1:1 ratio when the source and target sizes are very close.

Arrange multiple PDF pages on a single sheet (N-up) in Rust

Tile 2, 4, 6, or 9 source pages onto a single output sheet, useful for printing booklets or handouts.

rust
use pdfluent::{PdfDocument, nup::{NUpLayout, NUpOptions}};

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

    // 2-up: two A4 source pages side by side on one A3 sheet
    let opts = NUpOptions::new(NUpLayout::TwoUp);
    let output = doc.nup(opts)?;

    output.save("2up.pdf")?;
    Ok(())
}
  1. Open the source document

    The source pages are scaled and positioned into the output sheet. The original file is not modified.

    rust
    let doc = PdfDocument::open("input.pdf")?;
  2. Choose an N-up layout

    NUpLayout provides common configurations. Each layout specifies the grid dimensions and the output sheet size.

    rust
    use pdfluent::nup::NUpLayout;
    
    let layout = NUpLayout::TwoUp;    // 1x2 grid, landscape A4
    // NUpLayout::FourUp   // 2x2 grid, A4
    // NUpLayout::SixUp    // 2x3 grid, A4
    // NUpLayout::NineUp   // 3x3 grid, A4
    // NUpLayout::Custom { cols: 3, rows: 2, sheet: PageSize::A3 }
  3. Configure margins and gaps

    NUpOptions lets you set the gap between cells and the outer margin in points.

    rust
    use pdfluent::nup::NUpOptions;
    
    let opts = NUpOptions::new(NUpLayout::FourUp)
        .gap(8.0)       // 8 pt gap between cells
        .margin(20.0);  // 20 pt outer margin
  4. Build the N-up document

    Call doc.nup(opts) to produce a new Document where each output page contains the specified number of source pages.

    rust
    let output = doc.nup(opts)?;
  5. Save and verify

    Write the output file. The page count of the output is ceil(source_pages / n).

    rust
    println!(
        "Input pages: {}, Output pages: {}",
        doc.page_count(),
        output.page_count(),
    );
    output.save("nup_output.pdf")?;
  • Source pages are scaled proportionally to fit each cell. Aspect ratio is preserved; cells may have blank margins.
  • For booklet printing (saddle-stitch), use NUpLayout::Booklet which handles page imposition order automatically.
  • Annotations on source pages are scaled along with the content and remain functional in the output.
  • N-up output page size defaults to the same size as the source. Override with NUpOptions::sheet_size(PageSize::A3).

Get the page count of a PDF in Rust

Read the total number of pages from a PDF file. PDFluent reads the page count from the document catalog without parsing every page's content stream.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("file.pdf")?;
    println!("{} pages", doc.page_count());
    Ok(())
}
  1. Open the PDF and call page_count()

    page_count() reads the /Count entry in the PDF page tree. It doesn't parse page content streams; for a well-formed PDF the call is effectively O(1).

    rust
    use pdfluent::prelude::*;
    
    let doc = PdfDocument::open("invoice.pdf")?;
    let count = doc.page_count();
    println!("This PDF has {} page(s)", count);
  2. Get page count from bytes

    If the PDF is already in memory (from a network response or database blob), use from_bytes() instead of open().

    rust
    let pdf_bytes: Vec<u8> = fetch_pdf_from_database()?;
    let doc = PdfDocument::from_bytes(&pdf_bytes)?;
    println!("Pages: {}", doc.page_count());
  3. Walk the page tree when /Count is suspect

    Some malformed PDFs have an incorrect /Count entry. If you need a physically-verified count, iterate doc.pages() and count as you go — each iteration step resolves the next page from the tree.

    rust
    let doc = PdfDocument::open("maybe_corrupt.pdf")?;
    let physical_count = doc.pages().count();
    let declared_count = doc.page_count();
    assert_eq!(physical_count, declared_count, "declared /Count != physical pages");
  4. Batch page counts for a directory of PDFs

    Loop over files and collect counts. PdfDocument opens in-memory, so drop each doc between iterations to keep allocations bounded.

    rust
    use pdfluent::prelude::*;
    use std::fs;
    
    for entry in fs::read_dir("./invoices")? {
        let path = entry?.path();
        if path.extension().map(|e| e == "pdf").unwrap_or(false) {
            match PdfDocument::open(&path) {
                Ok(doc) => println!("{}: {} pages", path.display(), doc.page_count()),
                Err(e) => eprintln!("{}: error - {}", path.display(), e),
            }
        }
    }
  • page_count() returns a usize. A valid PDF always has at least 1 page.
  • Encrypted PDFs require decryption before the page count is accessible. Open them with PdfDocument::open_with(path, OpenOptions::new().with_password("...")).
  • If you need a physically-accurate count against a potentially-malformed /Count entry, walk doc.pages().count() as shown in step 4.
  • Page indexing in the 1.0 SDK is 1-based throughout (RFC 0001 §1).

Get page width, height, and media box in Rust

Read the MediaBox, CropBox, and BleedBox from any PDF page. Convert between points and millimetres or inches for printing and layout workflows.

rust
use pdfluent::PdfDocument;

fn main() -> pdfluent::Result<()> {
    let doc = PdfDocument::open("file.pdf")?;
    for i in 0..doc.page_count() {
        let (w, h) = doc.page(i)?.dimensions();
        println!("page {}: {} x {} pt", i + 1, w, h);
    }
    Ok(())
}
  1. Open the PDF and access pages

    Open the document and iterate or index pages directly.

    rust
    use pdfluent::PdfDocument;
    
    let doc = PdfDocument::open("blueprint.pdf")?;
    let page = doc.page(1)?; // first page
  2. Read the MediaBox

    The MediaBox defines the full physical extent of the page in PDF user units (points). 1 point = 1/72 inch.

    rust
    let (width, height) = page.dimensions();
    println!("Width:  {:.2} pt", width);
    println!("Height: {:.2} pt", height);
  3. Read CropBox and BleedBox

    CropBox is the visible page area. BleedBox is for print bleed. Both fall back to the MediaBox if not set.

    rust
    // PDFluent exposes the effective page size via dimensions()
    let (width, height) = page.dimensions();
    println!("Page: {:.1} x {:.1} pt", width, height);
  4. Convert to millimetres and inches

    PDF points convert to mm with factor 25.4/72 and to inches with factor 1/72.

    rust
    fn pt_to_mm(pt: f64) -> f64 { pt * 25.4 / 72.0 }
    fn pt_to_inch(pt: f64) -> f64 { pt / 72.0 }
    
    let (w, h) = page.dimensions();
    println!(
        "Page size: {:.1} x {:.1} mm  ({:.3} x {:.3} in)",
        pt_to_mm(w), pt_to_mm(h), pt_to_inch(w), pt_to_inch(h),
    );
  • PDF units are always points (1/72 inch). There is no concept of DPI at the page dimension level.
  • A standard A4 page is 595 x 842 pt. Letter is 612 x 792 pt.
  • Pages in the same document can have different sizes. Always read dimensions per-page, not once for the document.
  • Rotate() entry affects display orientation. Use page.rotation() to read it and adjust width/height for display purposes.