Exis.PdfEditor — The .NET PDF Toolkit Exis.PdfEditor — The Python PDF Toolkit
Find & replace text in PDFs, merge, split, build, fill forms, redact, sign, optimize, and convert to PDF/A — all from your C# code. Zero dependencies. Lossless editing. Find & replace text in PDFs, merge, split, build, fill forms, redact, sign, optimize, and convert to PDF/A — all from your Python code. Native AOT-compiled binary, no .NET runtime required.
dotnet add package Exis.PdfEditor
Install-Package Exis.PdfEditor
pip install exis-pdfeditor
poetry add exis-pdfeditor
See it in Action
Watch a 5-minute walkthrough of the library — from dotnet add package to batch find & replace, regex patterns, and styled replacements. Console, WPF, and WinForms demo apps included.
The walkthrough below shows the C# library, but every API behaves identically in Python — same engine, same arguments, same results. Browse the Python sample app for the matching exis_pdfeditor.* calls.
Why Exis.PdfEditor?
Most PDF libraries destroy your document
Most libraries convert your PDF to an intermediate format (HTML, image, or DOM), apply changes, then rebuild the entire file from scratch. This destructive render-rebuild cycle inevitably breaks the original document.
What gets broken:
- Interactive form fields are flattened or lost
- Digital signatures are invalidated
- Character spacing and kerning are altered
- Font substitution changes the appearance
- Bookmarks, links, and annotations are dropped
Exis.PdfEditor edits content streams directly
Exis.PdfEditor parses the actual PDF content streams and performs surgical replacements at the operator level. No intermediate format. No rebuild. The output is the same file with only the targeted changes applied.
What gets preserved:
- All form fields remain fully interactive
- Digital signatures stay valid on unmodified pages
- Original character spacing is maintained exactly
- Embedded fonts are reused, not substituted
- Bookmarks, annotations, and metadata are untouched
Other Libraries
Exis.PdfEditor
Code Samples
Find & Replace Text (3 Lines of Code)
using Exis.PdfEditor;
using Exis.PdfEditor.Licensing;
ExisLicense.Initialize(); // Complimentary 14-day trial - no key needed
var result = PdfFindReplace.Execute(
"contract.pdf",
"contract-updated.pdf",
"Acme Corporation",
"Globex Industries");
Console.WriteLine($"Replaced {result.TotalReplacements} occurrences " +
$"across {result.PagesModified} pages.");
Find & Replace Text (3 Lines of Code)
import exis_pdfeditor
exis_pdfeditor.initialize() # Complimentary 14-day trial - no key needed
result = exis_pdfeditor.find_replace(
"contract.pdf",
"contract-updated.pdf",
"Acme Corporation",
"Globex Industries",
)
print(f"Replaced {result.totalReplacements} occurrences "
f"across {result.pagesModified} pages.")
Multiple Find & Replace Pairs
var pairs = new[]
{
new FindReplacePair("2025", "2026"),
new FindReplacePair("Draft", "Final"),
new FindReplacePair("CONFIDENTIAL", "PUBLIC"),
};
var result = PdfFindReplace.Execute(
"report.pdf",
"report-final.pdf",
pairs);
Multiple Find & Replace Pairs
result = exis_pdfeditor.find_replace(
"report.pdf",
"report-final.pdf",
pairs=[
{"search": "2025", "replace": "2026"},
{"search": "Draft", "replace": "Final"},
{"search": "CONFIDENTIAL", "replace": "PUBLIC"},
],
)
Regex Pattern Matching
var options = new PdfFindReplaceOptions { UseRegex = true };
// Replace all US phone numbers with a placeholder
var result = PdfFindReplace.Execute(
"document.pdf",
"redacted.pdf",
@"\(\d{3}\)\s?\d{3}-\d{4}",
"[PHONE REDACTED]",
options);
Regex Pattern Matching
# Replace all US phone numbers with a placeholder
result = exis_pdfeditor.find_replace(
"document.pdf",
"redacted.pdf",
pairs=[
{
"search": r"\(\d{3}\)\s?\d{3}-\d{4}",
"replace": "[PHONE REDACTED]",
"isRegex": True,
},
],
)
Licensed Usage
// Purchase at pdfbatcheditor.com/developers - $499/developer/year
ExisLicense.Initialize("XXXX-XXXX-XXXX-XXXX");
// Unlimited pages, no restrictions, no console messages
var result = PdfFindReplace.Execute("large-doc.pdf", "output.pdf", "old", "new");
Licensed Usage
# Purchase at pdfbatcheditor.com/developers - $499/developer/year
exis_pdfeditor.initialize("XXXX-XXXX-XXXX-XXXX")
# Or set EXIS_PDF_LICENSE_KEY environment variable
# Unlimited pages, no restrictions, no console messages
result = exis_pdfeditor.find_replace("large-doc.pdf", "output.pdf", "old", "new")
Text Fitting Options
var options = new PdfFindReplaceOptions
{
CaseSensitive = true,
WholeWordOnly = false,
UseRegex = false,
UseIncrementalUpdate = true,
TextFitting = TextFittingMode.Adaptive, // Best quality text fitting
MinHorizontalScale = 70, // Minimum Tz percentage (50-100)
MaxFontSizeReduction = 1.5 // Max font size reduction in points
};
var result = PdfFindReplace.Execute(
"contract.pdf", "updated.pdf",
"Short Name", "A Much Longer Replacement Name That Needs Fitting",
options);
Text Fitting Options
result = exis_pdfeditor.find_replace(
"contract.pdf",
"updated.pdf",
"Short Name",
"A Much Longer Replacement Name That Needs Fitting",
case_sensitive=True,
whole_word=False,
use_regex=False,
use_incremental_update=True,
text_fitting="adaptive", # none | preserve_width | fit_to_page | adaptive
min_horizontal_scale=70, # Minimum Tz percentage (50-100)
max_font_size_reduction=1.5, # Max font size reduction in points
)
Text Formatting & Decorations
// Apply formatting to replacement text
var result = PdfFindReplace.Execute(
"input.pdf", "output.pdf",
"old text", "new text",
new PdfFindReplaceOptions
{
ReplacementTextColor = PdfColor.Red, // Font color of replaced text
ReplacementHighlightColor = PdfColor.Yellow, // Background highlight behind text
ReplacementBold = true, // Faux bold via fill+stroke rendering
ReplacementUnderline = true, // Draw underline below text
ReplacementStrikethrough = true // Draw line through text
});
Text Formatting & Decorations
# Apply formatting to replacement text
result = exis_pdfeditor.find_replace(
"input.pdf",
"output.pdf",
"old text",
"new text",
replacement_text_color={"r": 1, "g": 0, "b": 0}, # Red font
replacement_highlight_color={"r": 1, "g": 1, "b": 0}, # Yellow highlight
replacement_bold=True, # Faux bold
replacement_underline=True,
replacement_strikethrough=True,
)
Merge PDFs
// Merge multiple PDFs into one, preserving page dimensions and resources
byte[] merged = PdfMerger.Merge(new[] { "cover.pdf", "report.pdf", "appendix.pdf" });
File.WriteAllBytes("combined.pdf", merged);
// Or write directly to a file
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
});
Merge PDFs
# Merge multiple PDFs into one, preserving page dimensions and resources
exis_pdfeditor.merge(
["cover.pdf", "report.pdf", "appendix.pdf"],
"combined.pdf",
)
Split PDFs
// 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 with naming pattern
PdfSplitter.SplitToFiles("input.pdf", "page_{0}.pdf");
Split PDFs
# Split into individual page files
result = exis_pdfeditor.split("input.pdf", "output_folder/")
print(f"Split into {result.pageCount} files")
# Extract specific pages (1-based) into a new PDF
exis_pdfeditor.extract_pages("input.pdf", "subset.pdf", pages=[1, 3, 5])
Build PDFs 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(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);
Stamp / Overlay another PDF
# Overlay a letterhead on top of every page
result = exis_pdfeditor.stamp(
"document.pdf",
"stamped.pdf",
"letterhead.pdf",
mode="overlay",
)
print(f"Stamped {result.pagesStamped} pages")
# Or use as a background underlay
exis_pdfeditor.stamp(
"document.pdf",
"stamped.pdf",
"background.pdf",
mode="underlay",
opacity=0.5,
page_range=[1],
)
Extract Text
// Extract all text from a PDF
PdfTextResult text = PdfTextExtractor.ExtractText("input.pdf");
Console.WriteLine(text.FullText);
// Extract from specific pages only
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})");
Extract Text
# Extract all text from a PDF
result = exis_pdfeditor.extract_text("input.pdf")
print(result.fullText)
# Extract from specific pages only
partial = exis_pdfeditor.extract_text("input.pdf", pages=[1, 3])
# Structured extraction with position and font data
structured = exis_pdfeditor.extract_text_structured("input.pdf")
for block in structured.pages[0].textBlocks:
print(f"[{block.x:.0f},{block.y:.0f}] {block.text} "
f"(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}");
Inspect Document (No License Required)
info = exis_pdfeditor.inspect("input.pdf")
print(f"Pages: {info.pageCount}")
print(f"Title: {info.title}")
print(f"Fonts: {', '.join(info.fontsUsed)}")
print(f"Encrypted: {info.isEncrypted}")
print(f"Form fields: {info.formFieldCount}")
Image Replacement
// Find all images in a PDF
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)}");
// 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 } });
Image Replacement
# Find all images in a PDF
result = exis_pdfeditor.find_images("input.pdf")
for img in result.images:
print(f"Image {img.index}: {img.pixelWidth}x{img.pixelHeight} "
f"{img.colorSpace} {img.format} on page(s) {img.pageNumbers}")
# Replace all images with a new one
result = exis_pdfeditor.replace_image("input.pdf", "output.pdf", "new-logo.jpg")
print(f"Replaced {result.imagesReplaced} of {result.imagesFound} images")
# Replace specific images by index, scoped to a page
exis_pdfeditor.replace_image(
"input.pdf", "output.pdf", "new-logo.jpg",
image_indices=[0, 2],
page_range=[1],
scale_mode="scale_to_fit",
)
Auto-Layout Document Builder
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)
.AlternatingRowBackground(0.95, 0.95, 1.0)
.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();
Page Editing (rotate, crop, reorder, delete)
# Rotate all pages 90 degrees clockwise
exis_pdfeditor.rotate("in.pdf", "rotated.pdf", angle=90)
# Rotate only specific pages
exis_pdfeditor.rotate("in.pdf", "rotated.pdf", angle=180, pages=[2, 4])
# Crop pages to a rectangle
exis_pdfeditor.crop(
"in.pdf", "cropped.pdf",
rect={"x": 50, "y": 50, "width": 500, "height": 700},
)
# Reorder pages
exis_pdfeditor.reorder("in.pdf", "reordered.pdf", order=[3, 1, 2])
# Delete pages
exis_pdfeditor.delete_pages("in.pdf", "trimmed.pdf", pages=[2, 4])
Form Filling
// Read form fields
List<PdfFormField> fields = PdfFormFiller.GetFields("form.pdf");
foreach (var field in fields)
Console.WriteLine($"{field.Name} ({field.FieldType}) = {field.CurrentValue}");
// Fill 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, remove interactive fields)
PdfFormFiller.Flatten("filled.pdf", "flattened.pdf");
Form Filling
# Read form fields
fields = exis_pdfeditor.list_fields("form.pdf")
for field in fields:
print(f"{field.name} ({field.type}) = {field.value}")
# Fill fields
result = exis_pdfeditor.fill_form(
"form.pdf", "filled.pdf",
fields={
"FirstName": "John",
"LastName": "Doe",
"State": "CA",
"AgreeToTerms": "Yes", # checkbox
},
)
print(f"Filled {result.fieldsFilled} fields")
# Flatten in one call - merge values into page content
exis_pdfeditor.fill_form(
"form.pdf", "flattened.pdf",
fields={"FirstName": "John"},
flatten=True,
)
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 specific page
new PdfRedaction { PageNumber = 3, Area = new PdfRect(100, 200, 300, 50) }
});
Console.WriteLine($"Applied {result.RedactionsApplied} redactions");
Redaction
result = exis_pdfeditor.redact(
"input.pdf", "redacted.pdf",
redactions=[
# Text-based redaction
{"text": "CONFIDENTIAL"},
# Regex pattern (e.g., SSN)
{"text": r"\d{3}-\d{2}-\d{4}", "isRegex": True},
# Replace with alternative text
{"text": "SECRET", "replaceWith": "[REDACTED]"},
# Area-based redaction on a specific page
{
"area": {"x": 100, "y": 200, "width": 300, "height": 50},
"pageNumber": 3,
},
],
)
print(f"Applied {result.redactionsApplied} redactions")
Optimization
var result = PdfOptimizer.Optimize("input.pdf", "optimized.pdf", new PdfOptimizeOptions
{
CompressStreams = true,
RemoveDuplicateObjects = true,
RemoveMetadata = false,
DownsampleImages = true,
MaxImageDpi = 150
});
Console.WriteLine($"Saved {result.BytesSaved} bytes ({result.ReductionPercent:F1}%)");
Console.WriteLine($"Images downsampled: {result.ImagesDownsampled}");
Optimization
result = exis_pdfeditor.optimize(
"input.pdf", "optimized.pdf",
downsample_images=True,
max_image_dpi=150,
remove_metadata=False,
)
print(f"Reduced {result.originalSize:,} -> {result.optimizedSize:,} bytes "
f"({result.reductionPercent:.1f}% smaller)")
print(f"Images downsampled: {result.imagesDownsampled}")
Digital Signatures (.NET 8, 9, 10+)
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}");
Console.WriteLine($"Certificate: {info.CertificateSubject}");
Console.WriteLine($"Issuer: {info.CertificateIssuer}");
Console.WriteLine($"Timestamp: {info.HasTimestamp}");
// Verify all signatures in a multi-signed document
List<PdfSignatureInfo> all = PdfSigner.VerifyAll("multi-signed.pdf");
foreach (var sig in all)
Console.WriteLine($"{sig.SignerName}: valid={sig.IsValid}");
Digital Signatures
# Sign with an invisible signature
exis_pdfeditor.sign(
"input.pdf", "signed.pdf",
cert_path="certificate.pfx",
cert_password="password",
reason="Approved",
location="New York",
signer_name="John Doe",
)
# Sign with a visible signature box on page 1
exis_pdfeditor.sign(
"input.pdf", "signed.pdf",
cert_path="certificate.pfx",
cert_password="password",
visible=True,
page=1,
rect={"x": 50, "y": 50, "width": 200, "height": 60},
)
# Verify a signed PDF
info = exis_pdfeditor.verify("signed.pdf")
print(f"Signed: {info.isSigned}")
print(f"Valid: {info.isValid}")
print(f"Signer: {info.signerName}")
print(f"Reason: {info.reason}")
# Verify all signatures in a multi-signed document
all_sigs = exis_pdfeditor.verify("multi-signed.pdf", all_signatures=True)
for sig in all_sigs:
print(f"{sig.signerName}: valid={sig.isValid}")
PDF/A Compliance
// Validate (no license required)
// Levels: PdfA1b, PdfA2b, PdfA2u, PdfA3b, PdfA3u
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);
PDF/A Compliance
# Validate (no license required)
# Levels: 1b, 2b, 2u, 3b, 3u
result = exis_pdfeditor.pdfa_validate("input.pdf", level="2b")
print(f"Compliant: {result.isCompliant}")
for v in result.violations:
print(f" [{v.code}] {v.message} (auto-fix: {v.canAutoFix})")
# Convert to PDF/A
exis_pdfeditor.pdfa_convert("input.pdf", "output-pdfa.pdf", level="2b")
Async API
// All I/O operations have async overloads with CancellationToken support
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);
// Pattern: ClassName.MethodNameAsync(...) on all classes
Encrypt & Decrypt
# Encrypt with user (open) and owner (permissions) passwords
exis_pdfeditor.encrypt(
"document.pdf", "protected.pdf",
user_password="openme",
owner_password="secret",
permissions=["Print", "CopyText"],
# Available: Print, ModifyContents, CopyText, AddAnnotations,
# FillForms, PrintHighQuality, All
)
# Decrypt to remove protection
exis_pdfeditor.decrypt("protected.pdf", "unlocked.pdf", password="openme")
How Exis.PdfEditor Compares
| Feature | Exis.PdfEditor | IronPDF | Spire.PDF | Aspose.PDF | Syncfusion |
|---|---|---|---|---|---|
| Direct content stream editing | ✓ | ✗ Renders via HTML/Chromium | ✗ Redaction-style replacement | ✗ Fragment-based replacement | ✗ Redaction-style replacement |
| Preserve form fields | ✓ | ✗ | Partial | Partial | ✗ |
| Preserve digital signatures | ✓ On unmodified pages | ✗ | ✗ | ✗ | ✗ |
| Preserve character spacing | ✓ | ✗ | ✗ | Partial | ✗ |
| Zero native dependencies | ✓ Pure .NET | ✗ Requires Chromium | ✓ | ✓ | ✓ |
| DLL size | < 500 KB | ~250 MB | ~20 MB | ~40 MB | ~15 MB |
| Batch processing | ✓ Single-pass multi-pair | Manual loop | Manual loop | Manual loop | Manual loop |
| .NET Framework 4.8 | ✓ | ✓ | ✓ | ✓ | ✗ .NET 6+ only |
| Cross-platform | ✓ | ✓ | ✓ | ✓ | ✓ |
| Regex support | ✓ | ✓ | ✓ | ✓ | ✓ |
| Price (per dev/year) | $499 | $749 | $999 | $1,175 | $995* |
| Company HQ | 🇺🇸 USA | 🇺🇸 USA | 🇨🇳 China | 🇦🇺 Australia | 🇺🇸 USA |
* Syncfusion pricing requires a platform license. Prices are approximate and based on publicly available information as of 2026. Contact vendors for current pricing.
"Direct content stream editing" means the library modifies PDF content stream operators in-place rather than converting to an intermediate format and rebuilding.
Everything You Need for PDF Processing
Direct Content Stream Editing
Edit PDF text at the operator level. No intermediate format, no render-rebuild. Your document comes out identical except for the targeted changes.
Zero Native Dependencies
Pure .NET assembly under 500 KB. No Chromium, no native DLLs, no system-level installs. Works everywhere .NET runs.
Lossless Editing
Forms, signatures, spacing, fonts, bookmarks, and annotations are preserved. Incremental updates keep signatures valid on unmodified pages.
Multi-Target Framework
Targets .NET Standard 2.0 (Framework 4.6.1+, Core 2.0+) and .NET 8/9/10+ with optimized builds and digital signature support.
Batch Processing
Process multiple find/replace pairs in a single pass. Built for high-throughput scenarios with thousands of documents.
Regex Support
Full .NET regex engine for pattern-based find and replace. Redact SSNs, phone numbers, emails, or any custom pattern.
Cross-Platform
Runs on Windows, Linux, and macOS. Deploy to Azure, AWS, Docker, or on-premises servers without platform-specific dependencies.
Small Footprint
Under 500 KB total. No bloated runtime, no embedded browser engine. Minimal memory usage even on large documents.
PDF Merge
Combine multiple PDFs into one document, preserving page dimensions and resources.
PDF Split
Extract individual pages or page ranges into separate PDFs. Split to files with naming patterns.
PDF Builder
Create PDFs from scratch with a fluent API. Add text, images, lines, and rectangles with full formatting control.
Text Extraction
Pull text content from PDF pages. Extract from all pages or specific page ranges.
Document Inspector
Read metadata, fonts, page dimensions, and form field counts. Works without any license.
Image Replacement
Find, analyze, and replace images in PDFs. Swap logos or graphics by index or page range with JPEG/PNG.
Auto-Layout Builder
Create reports with auto-pagination, text wrapping, tables, headers/footers, and page numbers.
Form Filling
Read and fill AcroForm fields including text, checkbox, and dropdown. Lossless form preservation.
Redaction
Text-based, regex pattern, or area-based redaction. Permanently remove sensitive content from PDFs.
Optimization
Compress streams, remove duplicate objects, and reduce file size while preserving document quality.
Digital Signatures
Sign PDFs with X.509 certificates and verify existing signatures. Available on .NET 8, 9, 10+.
Encryption & Security
AES-256 encryption with user/owner passwords. Granular permissions (print, copy, edit, annotate, forms). Decrypt to remove protection.
PDF/A Compliance
Validate and convert to PDF/A (1b, 2b, 2u, 3b, 3u) for long-term archival. Validation works without a license.
Async API
All I/O operations have async overloads with CancellationToken support for scalable applications.
Simple, Transparent Pricing
- Unlimited pages per document
- Unlimited files per project
- All features included
- Email support from the developer
- 14-day complimentary trial included
- Unlimited pages per document
- Unlimited files per project
- All features included
- Email support from the developer
- Automatic annual renewal
Start with a complimentary 14-day trial. No credit card required. Just install the NuGet package and call ExisLicense.Initialize() with no arguments.
Start with a complimentary 14-day trial. No credit card required. Just pip install exis-pdfeditor and call exis_pdfeditor.initialize() with no arguments.
All prices in USD. License keys are delivered via email within minutes of purchase. Volume discounts available for 5+ developers — contact support@exisone.com.
How the Complimentary Trial Works
Install & Initialize
- Install via NuGet:
dotnet add package Exis.PdfEditor - Call
ExisLicense.Initialize()with no arguments - 14-day trial starts automatically
- No registration, no credit card, no email required
Install & Initialize
- Install via pip:
pip install exis-pdfeditor - Call
exis_pdfeditor.initialize()with no arguments - 14-day trial starts automatically
- No registration, no credit card, no email required
Trial Limitations
- Maximum 3 pages processed per document after expiry
- Console message printed on each operation
- All API features are fully available
- No watermarks added to output
- Trial resets if you clear local app data
Upgrade to Licensed
- Purchase a license key above ($499/year)
- Pass your key:
ExisLicense.Initialize("YOUR-KEY") - Unlimited pages, no console messages
- Same API, same code — just add your key
Upgrade to Licensed
- Purchase a license key above ($499/year)
- Pass your key:
exis_pdfeditor.initialize("YOUR-KEY") - Or set
EXIS_PDF_LICENSE_KEYenvironment variable - Unlimited pages, same API — just add your key
No code changes needed when upgrading — just add your license key to the Initialize() call.
Built by a Team You Can Trust
Frequently Asked Questions
Python: Python 3.9 or later. Native AOT-compiled binary bundled in the wheel — no .NET runtime required. Platform wheels for Windows x64, Linux x64, and macOS ARM64 (Apple Silicon).
ExisLicense.Initialize() with no arguments. The trial starts automatically — no registration, no credit card, no email.
Python: Run
pip install exis-pdfeditor and call exis_pdfeditor.initialize() with no arguments. Same trial, same automatic activation.
During the trial, all features are fully available with no restrictions. After expiry, you can still use the library on documents up to 3 pages.
Tj, TJ, '). Exis.PdfEditor parses these streams, locates the target text across operator boundaries, and performs surgical replacements at the byte level. The rest of the document — forms, signatures, metadata, fonts — remains completely untouched. Most other libraries convert the PDF to an intermediate format (HTML, DOM, or image), modify that, then rebuild the entire PDF, which destroys forms, signatures, and spacing.MinHorizontalScale and MaxFontSizeReduction options. When replacement text is shorter, the original spacing is preserved naturally. The TextFittingMode.Adaptive setting provides the best balance of quality and fit.PdfInspector.Inspect() and PdfAConverter.Validate() work without any license or trial. You can read page counts, metadata, font lists, form field counts, and run PDF/A validation completely free. Only operations that modify PDFs (find/replace, merge, split, etc.) require a license or active trial.Start Building with Exis.PdfEditor Today
Questions? Email support@exisone.com — you will hear back from the developer who wrote the code.