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.PdfEditorPDF toolkit for .NET — find/replace, merge, split, and build PDFs from scratch. 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+ (optimized) |
NuGet automatically selects the correct build for your project. 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");
// 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(jpegBytes, x: 300, y: 400, width: 200, height: 150))
.AddPage(page => page
.Size(PdfPageSize.Letter)
.AddText("Page 2", x: 72, y: 700, fontSize: 14))
.Build();
File.WriteAllBytes("output.pdf", pdf);
PdfTextResult text = PdfTextExtractor.ExtractText("input.pdf");
Console.WriteLine(text.FullText);
// Extract from specific pages
PdfTextResult partial = PdfTextExtractor.ExtractText("input.pdf", new[] { 1, 3 });
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);
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(jpegBytes, x, y, width, height) // JPEG image
.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)
PdfTextResult ExtractText(string path);
PdfTextResult ExtractText(Stream stream);
PdfTextResult ExtractText(string path, int[] pages);
PdfTextResult ExtractText(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.
| 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 works 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.