Exis.PdfEditor 3.5.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Exis.PdfEditor --version 3.5.0
                    
NuGet\Install-Package Exis.PdfEditor -Version 3.5.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Exis.PdfEditor" Version="3.5.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Exis.PdfEditor" Version="3.5.0" />
                    
Directory.Packages.props
<PackageReference Include="Exis.PdfEditor" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Exis.PdfEditor --version 3.5.0
                    
#r "nuget: Exis.PdfEditor, 3.5.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Exis.PdfEditor@3.5.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Exis.PdfEditor&version=3.5.0
                    
Install as a Cake Addin
#tool nuget:?package=Exis.PdfEditor&version=3.5.0
                    
Install as a Cake Tool

Exis.PdfEditor

Comprehensive PDF toolkit for .NET — find/replace, merge, split, build, form filling, redaction, optimization, digital signatures, and PDF/A compliance. Operates directly on PDF content streams with zero external dependencies.

Platform Compatibility

Build Use with
netstandard2.0 .NET Framework 4.6.1+, .NET Core 2.0+, .NET 5, .NET 6, .NET 7
net8.0 .NET 8, .NET 9, .NET 10+ (optimized, adds digital signature support)

NuGet automatically selects the correct build for your project. .NET 9 and .NET 10 projects use the net8.0 build with full feature support. No additional configuration required.

dotnet add package Exis.PdfEditor

Samples: github.com/exisllc/Exis.PdfEditor-Samples

Features

  • Find & Replace — regex, case-insensitive, whole-word matching with width-aware text fitting, replacement text styling (color, highlight, bold, underline, strikethrough)
  • Merge — combine multiple PDFs with optional page range selection
  • Split — extract individual pages or page ranges into separate PDFs
  • Build — create PDFs from scratch with absolute positioning or auto-layout (tables, pagination, headers/footers)
  • Form Filling — read and fill AcroForm fields (text, checkbox, radio, dropdown/listbox), intelligent display labels from nearby page text, flatten forms to static PDF
  • Redaction — remove sensitive text or areas with black rectangles (regex supported)
  • Image Editor — find all images with displayable bytes (JPEG/BMP), replace all or specific images with JPEG/PNG, configurable scaling (match original, keep replacement size, scale to fit)
  • Watermark — add text watermarks with configurable position, font size, color, opacity, and page selection
  • Page Editing — rotate, crop, reorder, insert blank pages, delete pages
  • Stamping — overlay or underlay a PDF on top of/behind another PDF's pages, with opacity control
  • Encryption — decrypt password-protected PDFs (AES-256, AES-128, RC4-128), encrypt with password and permission control
  • Optimization — compress streams, deduplicate objects, strip metadata, downsample images
  • Digital Signatures — sign with X.509 certificates (invisible or visible), verify single or all signatures, certificate metadata (net8.0+)
  • PDF/A Compliance — validate and convert to PDF/A-1b, 2b, 2u, 3b, 3u
  • Extract — pull text content with optional position and font metadata
  • Inspect — read metadata, fonts, page dimensions (no license required)
  • Async — all I/O APIs have async overloads with CancellationToken support
  • Zero dependencies — no iTextSharp, no PDFsharp, no Aspose
  • Lossless — preserves form fields, checkboxes, digital signatures, layout

Quick Start

using Exis.PdfEditor;
using Exis.PdfEditor.Licensing;

// Start a 14-day trial — no key required - no limits
ExisLicense.Initialize();

// When you're ready, pass your activation key after the end of evaluation period:
// ExisLicense.Initialize("XXXX-XXXX-XXXX-XXXX");

Find & Replace

var result = PdfFindReplace.Execute("input.pdf", "output.pdf", "old text", "new text");
Console.WriteLine($"Replaced {result.TotalReplacements} occurrences on {result.PagesModified} pages");

// Style replacement text with color, highlight, and decorations
var styled = PdfFindReplace.Execute("input.pdf", "output.pdf", "old text", "new text",
    new PdfFindReplaceOptions
    {
        ReplacementTextColor = PdfColor.Red,           // Red replacement text
        ReplacementHighlightColor = PdfColor.Yellow,   // Yellow background highlight
        ReplacementBold = true,                        // Faux bold (fill + stroke)
        ReplacementUnderline = true,                   // Underline below text
        ReplacementStrikethrough = true                // Strikethrough line
    });

// Stream-based (for in-memory processing)
var input = new MemoryStream(File.ReadAllBytes("input.pdf"));
var output = new MemoryStream();
PdfFindReplace.Execute(input, output, "old text", "new text",
    new PdfFindReplaceOptions
    {
        CaseSensitive = false,
        TextFitting = TextFittingMode.Adaptive,
        ReplacementTextColor = PdfColor.Red,
        ReplacementBold = true,
        ReplacementUnderline = true
    });
File.WriteAllBytes("output.pdf", output.ToArray());

// Multiple replacements processed sequentially
// NOTE: Styling options must be set on EACH call's options — they don't persist.
var pairs = new[] { ("[Company]", "Acme Inc"), ("[Date]", "3/26/2026"), ("[City]", "Anytown") };
byte[] current = File.ReadAllBytes("template.pdf");
foreach (var (search, replace) in pairs)
{
    var inp = new MemoryStream(current);
    var outp = new MemoryStream();
    PdfFindReplace.Execute(inp, outp, search, replace,
        new PdfFindReplaceOptions
        {
            CaseSensitive = false,
            TextFitting = TextFittingMode.Adaptive,
            ReplacementTextColor = PdfColor.Red
        });
    current = outp.ToArray();
}
File.WriteAllBytes("filled.pdf", current);

Merge PDFs

byte[] merged = PdfMerger.Merge(new[] { "file1.pdf", "file2.pdf", "file3.pdf" });
File.WriteAllBytes("merged.pdf", merged);

// Or merge to file directly
PdfMerger.MergeToFile(new[] { "file1.pdf", "file2.pdf" }, "merged.pdf");

// Merge with page range selection
byte[] selected = PdfMerger.Merge(new[]
{
    new PdfMergeInput(File.ReadAllBytes("doc1.pdf"), new[] { 1, 3, 5 }),
    new PdfMergeInput(File.ReadAllBytes("doc2.pdf"))  // all pages
});

Split PDF

// Split into individual pages
List<byte[]> pages = PdfSplitter.Split("input.pdf");

// Extract specific pages (1-based)
byte[] subset = PdfSplitter.ExtractPages("input.pdf", new[] { 1, 3, 5 });

// Split to individual files
PdfSplitter.SplitToFiles("input.pdf", "page_{0}.pdf");

Build a PDF from Scratch

byte[] pdf = PdfBuilder.Create()
    .WithMetadata(m => m.Title("Report").Author("Exis"))
    .AddPage(page => page
        .Size(PdfPageSize.A4)
        .AddText("Hello, World!", x: 72, y: 750, fontSize: 24,
            options: o => o.Font("Helvetica").Bold().Color(0, 0, 0.8))
        .AddText("Generated with Exis.PdfEditor", x: 72, y: 720, fontSize: 12)
        .AddLine(72, 710, 523, 710, strokeWidth: 1)
        .AddRectangle(72, 600, 200, 80, fill: true,
            fillRed: 0.95, fillGreen: 0.95, fillBlue: 1.0)
        .AddImage(imageBytes, x: 300, y: 400, width: 200, height: 150)) // JPEG or PNG
    .AddPage(page => page
        .Size(PdfPageSize.Letter)
        .AddText("Page 2", x: 72, y: 700, fontSize: 14))
    .Build();

File.WriteAllBytes("output.pdf", pdf);

Build a Document with Auto-Layout

byte[] pdf = PdfDocumentBuilder.Create()
    .PageSize(PdfPageSize.A4)
    .Margins(72)
    .WithMetadata(m => m.Title("Report").Author("Exis"))
    .Header(h => h
        .AddText("Quarterly Report", PdfHorizontalAlignment.Center, 12, o => o.Bold())
        .AddLine())
    .Footer(f => f
        .AddLine()
        .AddPageNumber())  // "Page 1 of 3"
    .AddParagraph("Introduction", 18, o => o.Bold())
    .AddSpacing(8)
    .AddParagraph("This report covers Q1 results.")
    .AddSpacing(12)
    .AddTable(t => t
        .Columns(2, 1, 1)
        .HeaderRow(r => r.AddCell("Product").AddCell("Units").AddCell("Revenue"))
        .AddRow(r => r.AddCell("Widget A").AddCell("1,200").AddCell("$24,000"))
        .AddRow(r => r.AddCell("Widget B").AddCell("850").AddCell("$17,000")))
    .AddPageBreak()
    .AddParagraph("Appendix", 14, o => o.Bold())
    .Build();

Features: auto-pagination, text wrapping, tables with headers repeated on page breaks, headers/footers with page numbers, horizontal rules, images, spacing.

Form Filling

// Read form fields (includes smart display labels resolved from nearby page text)
List<PdfFormField> fields = PdfFormFiller.GetFields("form.pdf");
foreach (var field in fields)
{
    string label = field.DisplayName ?? field.Name;
    Console.WriteLine($"{label} ({field.Type}) = {field.Value}");
    // e.g. "Single or Married filing separately (Checkbox) = "
    // e.g. "First name and middle initial (Text) = John"
}

// Fill form fields
var result = PdfFormFiller.Fill("form.pdf", "filled.pdf", new Dictionary<string, string>
{
    { "FirstName", "John" },
    { "LastName", "Doe" },
    { "State", "CA" },
    { "AgreeToTerms", "Yes" }  // checkbox
});
Console.WriteLine($"Filled {result.FieldsFilled} fields");

// Flatten form (merge field appearances into page content, remove interactive fields)
PdfFormFiller.Flatten("filled.pdf", "flattened.pdf");

PdfFormField: Name (fully qualified field name), DisplayName (human-readable label resolved from nearby page text, or null), Type (Text/Checkbox/Radio/Dropdown/Listbox/Signature), Value, Options (for choice fields), IsReadOnly.

Smart label detection: Many PDF forms use cryptic internal field names (e.g., f1_01, c1_1[0]). DisplayName resolves human-readable labels by analyzing nearby text on the page — checking right-of-field (checkbox/radio labels), left-of-field, above, and below. Handles multi-fragment labels split across font changes (e.g., "Single or Married filing separately" rendered as separate bold/normal text operations).

Redaction

var result = PdfRedactor.Redact("input.pdf", "redacted.pdf", new[]
{
    // Text-based redaction
    new PdfRedaction { Text = "CONFIDENTIAL" },

    // Regex pattern (e.g., SSN)
    new PdfRedaction { Text = @"\d{3}-\d{2}-\d{4}", IsRegex = true },

    // Replace with alternative text
    new PdfRedaction { Text = "SECRET", ReplaceWith = "[REDACTED]" },

    // Area-based redaction on a specific page
    new PdfRedaction { PageNumber = 3, Area = new PdfRect(100, 200, 300, 50) }
});
Console.WriteLine($"Applied {result.RedactionsApplied} redactions");

Image Editor

// Find all images in a PDF (includes displayable image bytes)
var found = PdfImageEditor.FindImages("input.pdf");
foreach (var img in found.Images)
{
    Console.WriteLine($"Image #{img.Index}: {img.PixelWidth}x{img.PixelHeight} {img.ColorSpace} {img.Format} " +
        $"on page(s) {string.Join(", ", img.PageNumbers)}");
    Console.WriteLine($"  Data: {img.Data.Length} bytes");  // JPEG or BMP bytes for display
}

// Display image thumbnail in WPF
var firstImage = found.Images[0];
if (firstImage.Data.Length > 0)
{
    var bmp = new BitmapImage();
    bmp.BeginInit();
    bmp.StreamSource = new MemoryStream(firstImage.Data);
    bmp.CacheOption = BitmapCacheOption.OnLoad;
    bmp.EndInit();
    myImageControl.Source = bmp;  // Works for both JPEG and BMP data
}

// Replace all images with a new one
byte[] newLogo = File.ReadAllBytes("new-logo.jpg");
var result = PdfImageEditor.ReplaceAll("input.pdf", "output.pdf", newLogo);
Console.WriteLine($"Replaced {result.ImagesReplaced} of {result.ImagesFound} images");

// Replace specific images by index or page range
var selective = PdfImageEditor.Replace("input.pdf", "output.pdf", newLogo,
    new PdfImageReplaceOptions { ImageIndices = new[] { 0, 2 } });

// Replace with scaling options
var scaled = PdfImageEditor.Replace("input.pdf", "output.pdf", newLogo,
    new PdfImageReplaceOptions
    {
        ScaleMode = ImageScaleMode.ScaleToFit  // Fit within original bounds, preserve aspect ratio
    });

ImageScaleMode:

Mode Behavior
MatchOriginalSize Default. Fills the exact same display rectangle as the original. May distort if aspect ratios differ.
KeepReplacementSize Displays at natural size, preserving the original DPI. Larger images appear larger, smaller appear smaller.
ScaleToFit Fits within the original rectangle while preserving aspect ratio. Centered, with empty space if needed.

Watermark

// Diagonal watermark across all pages (default)
PdfWatermark.AddText("input.pdf", "output.pdf", "CONFIDENTIAL");

// Top watermark, red, 50% opacity, specific pages
PdfWatermark.AddText("input.pdf", "output.pdf", "DRAFT", new PdfWatermarkOptions
{
    Position = WatermarkPosition.Top,
    FontSize = 36,
    TextColor = PdfColor.Red,
    Opacity = 0.5,
    PageRange = new[] { 1, 2, 3 }
});

Page Editing

// Rotate all pages 90 degrees clockwise
PdfPageEditor.Rotate("input.pdf", "rotated.pdf", 90);

// Rotate specific pages
PdfPageEditor.Rotate("input.pdf", "rotated.pdf", 180,
    new PdfPageEditOptions { PageRange = new[] { 1, 3 } });

// Crop pages (coordinates in points: 72pt = 1 inch)
PdfPageEditor.Crop("input.pdf", "cropped.pdf", new PdfRect(72, 72, 468, 648));

// Reorder pages (1-based)
PdfPageEditor.Reorder("input.pdf", "reordered.pdf", new[] { 3, 1, 2 });

// Delete pages
PdfPageEditor.DeletePages("input.pdf", "trimmed.pdf", new[] { 2, 4 });

// Insert blank pages
byte[] result = PdfPageEditor.InsertBlankPages(data, new[]
{
    new PdfBlankPageInsertion { AfterPage = 0, Size = PdfPageSize.A4 },  // before page 1
    new PdfBlankPageInsertion { AfterPage = 3, Size = PdfPageSize.Letter } // after page 3
});

Stamping (PDF Overlay/Underlay)

byte[] letterhead = File.ReadAllBytes("letterhead.pdf");

// Overlay: stamp on top of existing content
PdfStamper.Overlay("input.pdf", "stamped.pdf", letterhead);

// Underlay: stamp behind existing content (like a PDF-based watermark)
PdfStamper.Underlay("input.pdf", "branded.pdf", letterhead);

// With options: specific pages, partial opacity
PdfStamper.Overlay("input.pdf", "output.pdf", letterhead, new PdfStampOptions
{
    PageRange = new[] { 1 },       // first page only
    StampPageNumber = 1,           // use page 1 of stamp PDF
    Opacity = 0.5                  // 50% transparent
});

Encryption & Decryption

// Decrypt a password-protected PDF
PdfSecurity.Decrypt("protected.pdf", "unlocked.pdf", "password");

// Encrypt with AES-256 (strongest standard encryption)
PdfSecurity.Encrypt("input.pdf", "locked.pdf", new PdfEncryptOptions
{
    UserPassword = "open123",              // password to open
    OwnerPassword = "admin456",            // password for full access
    Permissions = PdfPermissions.Print | PdfPermissions.CopyText  // restricted permissions
});

// Check encryption status (no license required)
var info = PdfSecurity.GetEncryptionInfo("file.pdf");
Console.WriteLine($"Encrypted: {info.IsEncrypted}, Version: {info.Version}");

Optimization

var result = PdfOptimizer.Optimize("input.pdf", "optimized.pdf", new PdfOptimizeOptions
{
    CompressStreams = true,          // Compress uncompressed streams
    RemoveDuplicateObjects = true,  // Deduplicate identical objects
    RemoveMetadata = false,         // Keep metadata by default
    DownsampleImages = true,        // Reduce oversized images
    MaxImageDpi = 150               // Target DPI (default: 150)
});
Console.WriteLine($"Saved {result.BytesSaved} bytes ({result.ReductionPercent:F1}%)");
Console.WriteLine($"Images downsampled: {result.ImagesDownsampled}");

Digital Signatures (net8.0+)

using System.Security.Cryptography.X509Certificates;

// Sign a PDF (invisible signature)
var cert = new X509Certificate2("certificate.pfx", "password");
PdfSigner.Sign("input.pdf", "signed.pdf", new PdfSignOptions
{
    Certificate = cert,
    Reason = "Approved",
    Location = "New York",
    ContactInfo = "admin@example.com"
});

// Sign with a visible signature annotation on the page
PdfSigner.Sign("input.pdf", "signed.pdf", new PdfSignOptions
{
    Certificate = cert,
    Reason = "Final Approval",
    Location = "New York",
    SignerName = "Jane Doe",
    Visible = true,
    PageNumber = 1,
    Rectangle = new PdfSignatureRectangle(72, 50, 200, 60)  // x, y, width, height in points
});

// Verify a signed PDF
PdfSignatureInfo info = PdfSigner.Verify("signed.pdf");
Console.WriteLine($"Signed: {info.IsSigned}");
Console.WriteLine($"Valid: {info.IsValid}");
Console.WriteLine($"Signer: {info.SignerName}");
Console.WriteLine($"Certificate: {info.CertificateSubject}");
Console.WriteLine($"Issuer: {info.CertificateIssuer}");
Console.WriteLine($"Timestamp: {info.HasTimestamp}");

// Verify all signatures in a document
List<PdfSignatureInfo> all = PdfSigner.VerifyAll("multi-signed.pdf");
foreach (var sig in all)
    Console.WriteLine($"{sig.SignerName}: valid={sig.IsValid}");

PDF/A Compliance

// Validate (no license required)
PdfAValidationResult result = PdfAConverter.Validate("input.pdf", PdfALevel.PdfA2b);
Console.WriteLine($"Compliant: {result.IsCompliant}");
foreach (var v in result.Violations)
    Console.WriteLine($"  [{v.Code}] {v.Message} (auto-fix: {v.CanAutoFix})");

// Convert to PDF/A
byte[] pdfa = PdfAConverter.Convert("input.pdf", PdfALevel.PdfA2b);
File.WriteAllBytes("output-pdfa.pdf", pdfa);

Extract Text

// Simple extraction
PdfTextResult text = PdfTextExtractor.ExtractText("input.pdf");
Console.WriteLine(text.FullText);

// Extract from specific pages
PdfTextResult partial = PdfTextExtractor.ExtractText("input.pdf", new[] { 1, 3 });

// Structured extraction with position and font data
PdfStructuredTextResult structured = PdfTextExtractor.ExtractStructured("input.pdf");
foreach (var block in structured.Pages[0].TextBlocks)
    Console.WriteLine($"[{block.X:F0},{block.Y:F0}] {block.Text} " +
        $"(font={block.FontName}, size={block.FontSize})");

Inspect Document (No License Required)

PdfDocumentInfo info = PdfInspector.Inspect("input.pdf");
Console.WriteLine($"Pages: {info.PageCount}");
Console.WriteLine($"Title: {info.Title}");
Console.WriteLine($"Fonts: {string.Join(", ", info.FontsUsed)}");
Console.WriteLine($"Encrypted: {info.IsEncrypted}");
Console.WriteLine($"Form fields: {info.FormFieldCount}");

API Reference

PdfFindReplace

// File-based
PdfFindReplaceResult Execute(string inputPath, string outputPath,
    string searchText, string replaceText, PdfFindReplaceOptions? options = null);

// Stream-based
PdfFindReplaceResult Execute(Stream input, Stream output,
    string searchText, string replaceText, PdfFindReplaceOptions? options = null);

// Multiple pairs
PdfFindReplaceResult Execute(string inputPath, string outputPath,
    IEnumerable<FindReplacePair> pairs, PdfFindReplaceOptions? options = null);

PdfFindReplaceOptions

var options = new PdfFindReplaceOptions
{
    CaseSensitive = true,           // Case-sensitive matching (default: true)
    WholeWordOnly = false,          // Match whole words only
    UseRegex = false,               // Enable regex patterns
    UseIncrementalUpdate = true,    // Incremental PDF update (smaller output)
    PageRange = null,               // Limit to specific pages (null = all)

    // Text fitting — controls what happens when replacement is wider than original
    TextFitting = TextFittingMode.None,  // None | PreserveWidth | FitToPage | Adaptive
    MinHorizontalScale = 70,             // Minimum Tz percentage (50-100)
    MaxFontSizeReduction = 1.5,          // Max font size reduction in points (Adaptive only)

    // Replacement text styling
    ReplacementTextColor = PdfColor.Red,        // Change fill color of replacement text
    ReplacementHighlightColor = PdfColor.Yellow, // Draw colored rectangle behind replacement text
    ReplacementBold = false,                    // Faux bold via fill+stroke rendering
    ReplacementUnderline = false,               // Draw underline below replacement text
    ReplacementStrikethrough = false            // Draw strikethrough through replacement text
};

TextFittingMode

Mode Behavior
None No fitting. Text renders at natural size.
PreserveWidth Compress horizontally to match original text width exactly.
FitToPage Compress only enough to prevent overflow past the page edge.
Adaptive Progressive: character spacing, word spacing, horizontal scaling, font size. Best quality.

PdfMerger

byte[] Merge(string[] inputPaths);
byte[] Merge(Stream[] inputStreams);
byte[] Merge(byte[][] inputData);
byte[] Merge(IEnumerable<PdfMergeInput> inputs);  // With page range selection
void MergeToFile(string[] inputPaths, string outputPath);

PdfSplitter

List<byte[]> Split(string inputPath);          // One PDF per page
List<byte[]> Split(Stream inputStream);
List<byte[]> Split(byte[] inputData);

byte[] ExtractPages(string inputPath, int[] pageNumbers);   // Selected pages in one PDF
byte[] ExtractPages(Stream inputStream, int[] pageNumbers);
byte[] ExtractPages(byte[] inputData, int[] pageNumbers);

void SplitToFiles(string inputPath, string outputPattern);  // Pattern: "page_{0}.pdf"

PdfBuilder

PdfBuilder.Create()
    .WithMetadata(m => m
        .Title("...").Author("...").Subject("...").Creator("...").Keywords("..."))
    .AddPage(page => page
        .Size(PdfPageSize.A4)                         // A4, Letter, Legal, A3, A5, Tabloid
        .Size(widthPoints, heightPoints)               // Custom size (72pt = 1 inch)
        .AddText(text, x, y, fontSize, options?)       // Positioned text
        .AddImage(imageBytes, x, y, width, height)     // JPEG or PNG (auto-detected)
        .AddLine(x1, y1, x2, y2, strokeWidth?, r?, g?, b?)
        .AddRectangle(x, y, w, h, fill?, strokeWidth?,
            strokeRed?, strokeGreen?, strokeBlue?,
            fillRed?, fillGreen?, fillBlue?))
    .Build();              // Returns byte[]
    .BuildToFile(path);    // Write to file
    .BuildToStream(stream); // Write to stream

Built-in fonts (no embedding needed): Helvetica, Times-Roman, Courier — each with Bold, Italic, BoldItalic variants.

Text options:

.AddText("text", 72, 700, 14, o => o
    .Font("Helvetica")    // Font family
    .Bold()               // Bold variant
    .Italic()             // Italic variant
    .Color(1, 0, 0))      // RGB color (0.0 to 1.0)

PdfFormFiller

// Read form fields
List<PdfFormField> GetFields(string path);
List<PdfFormField> GetFields(byte[] pdfData);
List<PdfFormField> GetFields(Stream stream);

// Fill form fields
PdfFormFillResult Fill(string inputPath, string outputPath, Dictionary<string, string> fieldValues);
byte[] Fill(byte[] inputData, Dictionary<string, string> fieldValues);
byte[] Fill(Stream input, Dictionary<string, string> fieldValues);

// Flatten — merge field appearances into page content, remove interactive fields
void Flatten(string inputPath, string outputPath);
byte[] Flatten(byte[] inputData);
void Flatten(Stream input, Stream output);

PdfRedactor

PdfRedactionResult Redact(string inputPath, string outputPath, PdfRedaction[] redactions);
byte[] Redact(byte[] inputData, PdfRedaction[] redactions);
byte[] Redact(Stream input, PdfRedaction[] redactions);

PdfImageEditor

// Find images
PdfImageFinderResult FindImages(string inputPath);
PdfImageFinderResult FindImages(Stream input);

// Replace all images
PdfImageReplaceResult ReplaceAll(string inputPath, string outputPath, byte[] replacementImage);
PdfImageReplaceResult ReplaceAll(Stream input, Stream output, byte[] replacementImage);

// Replace with options (filter by page range or image index)
PdfImageReplaceResult Replace(string inputPath, string outputPath, byte[] replacementImage,
    PdfImageReplaceOptions? options = null);
PdfImageReplaceResult Replace(Stream input, Stream output, byte[] replacementImage,
    PdfImageReplaceOptions? options = null);

PdfImageReplaceOptions: PageRange (1-based page numbers, null = all), ImageIndices (0-based image indices from FindImages, null = all), ScaleMode (MatchOriginalSize | KeepReplacementSize | ScaleToFit, default: MatchOriginalSize).

PdfImageInfo: Index, PageNumbers, PixelWidth, PixelHeight, ColorSpace (RGB/Gray/CMYK/Indexed/Unknown), Format (JPEG/Flate/Raw/JPEG2000), BitsPerComponent, Data (displayable image bytes — JPEG for JPEG images, BMP for Flate/Raw/CMYK/Grayscale; load directly into WPF BitmapImage via MemoryStream).

PdfWatermark

PdfWatermarkResult AddText(string inputPath, string outputPath, string text, PdfWatermarkOptions? options = null);
PdfWatermarkResult AddText(Stream input, Stream output, string text, PdfWatermarkOptions? options = null);
byte[] AddText(byte[] inputData, string text, PdfWatermarkOptions? options = null);

PdfWatermarkOptions: Position (Top/Bottom/Center/Across, default: Across), FontSize (default: 48), TextColor (PdfColor, default: light gray), Opacity (0.0–1.0, default: 0.3), PageRange (1-based page numbers, null = all).

PdfOptimizer

PdfOptimizeResult Optimize(string inputPath, string outputPath, PdfOptimizeOptions? options = null);
byte[] Optimize(byte[] inputData, PdfOptimizeOptions? options = null);
byte[] Optimize(Stream input, PdfOptimizeOptions? options = null);

PdfOptimizeOptions: CompressStreams (default true), RemoveDuplicateObjects (default true), RemoveMetadata (default false), DownsampleImages (default false), MaxImageDpi (default 150). Image downsampling applies to FlateDecode (PNG-style) images; JPEG images are left untouched.

PdfSigner (net8.0+)

// Sign
byte[] Sign(byte[] inputData, PdfSignOptions options);
byte[] Sign(string inputPath, PdfSignOptions options);
void Sign(string inputPath, string outputPath, PdfSignOptions options);
byte[] Sign(Stream input, PdfSignOptions options);

// Verify first signature
PdfSignatureInfo Verify(byte[] pdfData);
PdfSignatureInfo Verify(string path);
PdfSignatureInfo Verify(Stream stream);

// Verify all signatures
List<PdfSignatureInfo> VerifyAll(byte[] pdfData);
List<PdfSignatureInfo> VerifyAll(string path);
List<PdfSignatureInfo> VerifyAll(Stream stream);

PdfSignOptions: Certificate (X509Certificate2, required), Reason, Location, ContactInfo, SignerName (defaults to certificate subject), Visible (default false — invisible signature), PageNumber (1-based, default 1), Rectangle (PdfSignatureRectangle with X/Y/Width/Height in points, defaults to bottom-left corner). When Visible = true, the signature renders as an annotation on the page showing signer name, date, reason, and location.

PdfSignatureInfo includes: IsSigned, IsValid, SignerName, SignDate, Reason, Location, CertificateSubject, CertificateIssuer, CertificateSerialNumber, CertificateNotBefore, CertificateNotAfter, HasTimestamp, TimestampDate.

PdfAConverter

// Validate (no license required)
PdfAValidationResult Validate(string path, PdfALevel level = PdfALevel.PdfA2b);
PdfAValidationResult Validate(byte[] pdfData, PdfALevel level = PdfALevel.PdfA2b);
PdfAValidationResult Validate(Stream stream, PdfALevel level = PdfALevel.PdfA2b);

// Convert
byte[] Convert(byte[] inputData, PdfALevel level = PdfALevel.PdfA2b);
byte[] Convert(string inputPath, PdfALevel level = PdfALevel.PdfA2b);
void Convert(string inputPath, string outputPath, PdfALevel level = PdfALevel.PdfA2b);
byte[] Convert(Stream input, PdfALevel level = PdfALevel.PdfA2b);

PdfALevel: PdfA1b, PdfA2b, PdfA2u, PdfA3b, PdfA3u. The "u" variants additionally require Unicode mappings (ToUnicode CMap) for all fonts.

PdfTextExtractor

// Simple extraction
PdfTextResult ExtractText(string path);
PdfTextResult ExtractText(Stream stream);
PdfTextResult ExtractText(string path, int[] pages);
PdfTextResult ExtractText(Stream stream, int[] pages);

// Structured extraction (with position and font data)
PdfStructuredTextResult ExtractStructured(string path);
PdfStructuredTextResult ExtractStructured(Stream stream);
PdfStructuredTextResult ExtractStructured(string path, int[] pages);
PdfStructuredTextResult ExtractStructured(Stream stream, int[] pages);

PdfInspector (No License Required)

PdfDocumentInfo Inspect(string path);
PdfDocumentInfo Inspect(Stream stream);

Returns: Version, PageCount, Title, Author, Producer, Creator, CreationDate, ModificationDate, IsEncrypted, HasFormFields, FormFieldCount, FontsUsed, per-page WidthInPoints/HeightInPoints/CharacterCount.

PdfDocumentBuilder (Auto-Layout)

PdfDocumentBuilder.Create()
    .PageSize(PdfPageSize.A4)                  // Or .PageSize(width, height)
    .Margins(72)                               // Or .Margins(top, right, bottom, left)
    .WithMetadata(m => m.Title("..."))
    .Header(h => h
        .AddText("Title", PdfHorizontalAlignment.Center, fontSize, options?)
        .AddPageNumber("Page {page} of {pages}", alignment?, fontSize?, options?)
        .AddLine(thickness?, r?, g?, b?))
    .Footer(f => /* same as header */)
    .AddParagraph(text, fontSize?, options?)    // Auto-wrapped, auto-paginated
    .AddSpacing(points)
    .AddHorizontalRule(thickness?, r?, g?, b?)
    .AddImage(imageBytes, width, height)
    .AddTable(t => t
        .Columns(2, 1, 1)                     // Relative widths
        .ColumnsFixed(200, 100, 100)           // Or fixed widths in points
        .BorderWidth(0.5).BorderColor(r, g, b)
        .CellPadding(4)
        .AlternatingRowBackground(0.95, 0.95, 1.0)  // Light blue on even rows
        .HeaderRow(r => r.AddCell("..."))      // Repeated on page breaks
        .AddRow(r => r
            .AddCell("...", o => o
                .VerticalAlignment(PdfVerticalAlignment.Middle)  // Top, Middle, Bottom
                .PaddingLeft(8).PaddingRight(8))))               // Per-side padding
    .AddPageBreak()
    .Build();              // Returns byte[]
    .BuildToFile(path);
    .BuildToStream(stream);

Async Overloads

Every I/O API has an async counterpart directly on the main class with CancellationToken support:

// Pattern: <ClassName>.<MethodName>Async(...)
byte[] merged = await PdfMerger.MergeAsync(inputPaths, cancellationToken);
PdfTextResult text = await PdfTextExtractor.ExtractTextAsync(stream, cancellationToken);
var info = await PdfInspector.InspectAsync(path, cancellationToken);
var result = await PdfOptimizer.OptimizeAsync(data, options, cancellationToken);
var sigs = await PdfSigner.VerifyAllAsync(path, cancellationToken);
// Also: PdfFindReplace.ExecuteAsync, PdfSplitter.SplitAsync,
//        PdfFormFiller.FillAsync/FlattenAsync, PdfRedactor.RedactAsync,
//        PdfImageEditor.FindImagesAsync/ReplaceAsync/ReplaceAllAsync,
//        PdfWatermark.AddTextAsync, PdfAConverter.ValidateAsync/ConvertAsync,
//        PdfPageEditor.RotateAsync/CropAsync/ReorderAsync/DeletePagesAsync,
//        PdfStamper.OverlayAsync/UnderlayAsync,
//        PdfSecurity.DecryptAsync/EncryptAsync

Evaluation & Licensing

Mode Limit How to activate
Trial Full access for 14 days ExisLicense.Initialize() (no key)
Evaluation 3-page limit After trial expires
Licensed Unlimited ExisLicense.Initialize("XXXX-XXXX-XXXX-XXXX")

PdfInspector, PdfAConverter.Validate, and PdfSecurity.GetEncryptionInfo work without any license.

Purchase at pdfbatcheditor.com/developers.

License

Copyright (c) Exis LLC 2024-2026. All rights reserved. Commercial license required for production use. See LICENSE.md for details.

Made in USA.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.7.8 92 5/11/2026
3.7.7 97 5/9/2026
3.7.5 89 5/9/2026
3.7.4 102 5/9/2026
3.7.3 98 5/8/2026
3.7.2 100 5/8/2026
3.7.1 95 5/8/2026
3.7.0 116 4/21/2026
3.6.4 98 4/21/2026
3.6.3 87 4/20/2026
3.6.2 100 4/20/2026
3.6.0 97 4/20/2026
3.5.7 95 4/20/2026
3.5.6 104 4/17/2026
3.5.5 92 4/17/2026
3.5.4 100 4/16/2026
3.5.3 107 4/10/2026
3.5.2 103 4/9/2026
3.5.1 104 4/9/2026
3.5.0 102 4/7/2026
Loading failed