Comprehensive PDF toolkit for .NET — find/replace, merge, split, build, form filling, redaction, image editing, watermark, optimization, digital signatures, and PDF/A compliance. Zero external dependencies. Operates directly on PDF content streams. Features: text find/replace (regex, case-insensitive, whole-word, width-aware fitting), PDF merging (with page range selection), page splitting/extraction, fluent document builder (text, JPEG/PNG images, shapes), structured text extraction (with positions and font data), form filling (AcroForm text/checkbox/choice fields), text and area redaction, image finding and replacement (JPEG/PNG, by index or page range), text watermarks (configurable position, font size, color, opacity, page selection), stream compression and object deduplication, digital signatures (PKCS#7, net8.0), PDF/A validation and conversion (1b/2b/3b), document inspection. The netstandard2.0 build is compatible with .NET Framework 4.6.1+, .NET Core 2.0+, and .NET 5/6/7/8/9/10+. The net8.0 build adds digital signature support and is used by .NET 8, 9, 10, and later. NuGet resolves automatically. Made in USA.
$ dotnet add package Exis.PdfEditorComprehensive 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.
| 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
using Exis.PdfEditor;
using Exis.PdfEditor.Licensing;
// Initialize (optional: pass license key for documents over 3 pages)
ExisLicense.Initialize("XXXX-XXXX-XXXX-XXXX");var result = PdfFindReplace.Execute("input.pdf", "output.pdf", "old text", "new text");
Console.WriteLine($"Replaced {result.TotalReplacements} occurrences on {result.PagesModified} pages");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 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");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);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.
// Read form fields
List<PdfFormField> fields = PdfFormFiller.GetFields("form.pdf");
foreach (var field in fields)
Console.WriteLine($"{field.Name} ({field.FieldType}) = {field.CurrentValue}");
// 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");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");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
});
Console.WriteLine($"Saved {result.BytesSaved} bytes ({result.ReductionPercent:F1}%)");using System.Security.Cryptography.X509Certificates;
// Sign a PDF
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"
});
// 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}");// 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);// 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})");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}");// 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);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)
};| 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. |
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);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.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 streamBuilt-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)// 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);PdfRedactionResult Redact(string inputPath, string outputPath, PdfRedaction[] redactions);
byte[] Redact(byte[] inputData, PdfRedaction[] redactions);
byte[] Redact(Stream input, PdfRedaction[] redactions);PdfOptimizeResult Optimize(string inputPath, string outputPath, PdfOptimizeOptions? options = null);
byte[] Optimize(byte[] inputData, PdfOptimizeOptions? options = null);
byte[] Optimize(Stream input, PdfOptimizeOptions? options = null);// 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
PdfSignatureInfo Verify(byte[] pdfData);
PdfSignatureInfo Verify(string path);
PdfSignatureInfo Verify(Stream stream);// 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);// 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);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.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)
.HeaderRow(r => r.AddCell("...")) // Repeated on page breaks
.AddRow(r => r.AddCell("...")))
.AddPageBreak()
.Build(); // Returns byte[]
.BuildToFile(path);
.BuildToStream(stream);Every I/O API has an async counterpart with CancellationToken support:
// Pattern: <ClassName>Async.<MethodName>Async(...)
byte[] merged = await PdfMergerAsync.MergeAsync(inputPaths, cancellationToken);
PdfTextResult text = await PdfTextExtractorAsync.ExtractTextAsync(stream, cancellationToken);
var info = await PdfInspectorAsync.InspectAsync(path, cancellationToken);
var result = await PdfOptimizerAsync.OptimizeAsync(data, options, cancellationToken);
// Also: PdfFindReplaceAsync, PdfSplitterAsync, PdfFormFillerAsync,
// PdfRedactorAsync, PdfSignerAsync, PdfAConverterAsync| 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 and PdfAConverter.Validate work without any license.
Purchase at officefindreplace.com.
Copyright (c) Exis LLC 2024-2026. All rights reserved. Commercial license required for production use. See LICENSE.md for details.
Made in USA.