Exis.PdfEditor
3.5.0
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
<PackageReference Include="Exis.PdfEditor" Version="3.5.0" />
<PackageVersion Include="Exis.PdfEditor" Version="3.5.0" />
<PackageReference Include="Exis.PdfEditor" />
paket add Exis.PdfEditor --version 3.5.0
#r "nuget: Exis.PdfEditor, 3.5.0"
#:package Exis.PdfEditor@3.5.0
#addin nuget:?package=Exis.PdfEditor&version=3.5.0
#tool nuget:?package=Exis.PdfEditor&version=3.5.0
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 | Versions 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. |
-
.NETStandard 2.0
- Microsoft.Bcl.HashCode (>= 6.0.0)
- Microsoft.Win32.Registry (>= 4.7.0)
- System.Buffers (>= 4.6.0)
- System.Memory (>= 4.6.0)
- System.Text.Json (>= 8.0.5)
-
net8.0
- Microsoft.Win32.Registry (>= 5.0.0)
- System.Security.Cryptography.Pkcs (>= 8.0.1)
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 |