C# で PDF ファイル内のテキストをプログラムで検索・置換。コンテンツストリームの直接編集 — DOCXへの変換不要、外部依存なし、データ損失なし。
dotnet add package Exis.PdfEditor
Install-Package Exis.PdfEditor
ほとんどの.NET PDFライブラリ — IronPDF、Spire.PDF、Aspose、Syncfusion — はPDFを中間形式に変換、テキストを墨消して新しいテキストを上に描画、またはページをゼロから再構築してテキストを置換します。
このアプローチは以下を破壊します:
Exis.PdfEditorはPDFコンテンツストリームをバイトレベルで直接解析します。実際のPDFオペレータ内のテキストを特定し、対象の文字列オペランドのみを変更し、PDFインクリメンタル更新で書き戻します。
変更されない部分はバイト単位で同一のままです:
using Exis.PdfEditor;
using Exis.PdfEditor.Licensing;
ExisLicense.Initialize(); // Free 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.");
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);
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);
// Purchase at officefindreplace.com/Home/pdf-find-replace-csharp - $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");
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);
// Color replacement text and add a highlight background
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
});
// 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
});
// 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");
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);
// 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})");
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}");
// 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 } });
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();
// 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");
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");
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}");
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}");
// 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);
// 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
| 機能 | Exis.PdfEditor | IronPDF | Spire.PDF | Aspose.PDF | Syncfusion |
|---|---|---|---|---|---|
| コンテンツストリームの直接編集 | ✓ | ✗ HTMLレンダリング | ✗ 墨消しオーバーレイ | ✗ フラグメント置換 | ✗ 墨消しオーバーレイ |
| フォームフィールドを保持 | ✓ | ✗ | 部分的 | 部分的 | ✗ |
| デジタル署名を保持 | ✓ 未変更ページ | ✗ | ✗ | ✗ | ✗ |
| テキスト間隔/カーニングを保持 | ✓ | ✗ | ✗ | 部分的 | ✗ |
| ネイティブ依存なし | ✓ 純粋な.NET | ✗ Chromiumエンジン | ✓ | ✓ | ✓ |
| DLLサイズ | < 500 KB | ~250 MB | ~20 MB | ~40 MB | ~15 MB |
| バッチマルチペア置換 | ✓ シングルパス | 手動ループ | 手動ループ | 手動ループ | 手動ループ |
| .NET Framework 4.8 | ✓ | ✓ | ✓ | ✓ | ✗ .NET 6+のみ |
| クロスプラットフォーム | ✓ | ✓ | ✓ | ✓ | ✓ |
| 正規表現サポート | ✓ | ✓ | ✓ | ✓ | ✓ |
| 価格(開発者/年) | $499 | $749 | $999 | $1,175 | $995* |
| 本社 | 🇺🇸 USA | 🇺🇸 USA | 🇨🇳 China | 🇦🇺 Australia | 🇺🇸 USA |
2026年2月時点の公開ドキュメントに基づく比較。機能サポートはバージョンにより異なる場合があります。
「コンテンツストリームの直接編集」とは、ライブラリがPDFテキストオペレータを変換、再レンダリング、オーバーレイなしにその場で変更することを意味します。
コンテンツストリームオペレータを変更。中間変換なし。
Ghostscript不要、LibreOffice不要、Chromium不要。純粋なマネージド.NET。
フォーム、署名、注釈、ブックマーク — すべて保持。
.NET 8, 9, 10+、.NET Standard 2.0 (.NET Framework 4.6.1+、.NET Core 2.0+、.NET 5-7)。
複数の検索/置換ペアを1回のパスで実行。
パターンベースの置換に完全な.NET regexサポート。
Windows、Linux、macOS。.NETが動くすべての場所で。
単一DLL、500 KB未満。デプロイするネイティブバイナリなし。
Combine multiple PDFs into one document, preserving page dimensions and resources.
Extract individual pages or page ranges into separate PDFs. Split to files with naming patterns.
Create PDFs from scratch with a fluent API. Add text, images, lines, and rectangles with full formatting control.
Pull text content from PDF pages. Extract from all pages or specific page ranges.
Read metadata, fonts, page dimensions, and form field counts. Works without any license.
Find, analyze, and replace images in PDFs. Swap logos or graphics by index or page range with JPEG/PNG.
Create reports with auto-pagination, text wrapping, tables, headers/footers, and page numbers.
Read and fill AcroForm fields including text, checkbox, and dropdown. Lossless form preservation.
Text-based, regex pattern, or area-based redaction. Permanently remove sensitive content from PDFs.
Compress streams, remove duplicate objects, and reduce file size while preserving document quality.
Sign PDFs with X.509 certificates and verify existing signatures. Available on .NET 8, 9, 10+.
Validate and convert to PDF/A (1b, 2b, 2u, 3b, 3u) for long-term archival. Validation works without a license.
All I/O operations have async overloads with CancellationToken support for scalable applications.
NuGetパッケージをインストールしてExisLicense.Initialize()を呼び出す — 14日間フル機能。トライアル後、評価モードはドキュメントあたり最大3ページを処理。透かしなし。準備ができたらofficefindreplace.com/Home/pdf-find-replace-csharpでライセンスキーを購入。
価格は米ドル。開発者あたり1キー。開発機、ビルドサーバー、本番環境で動作 — マシン単位やデプロイ単位の制限なし。
トライアルモードとライセンスモードでコードは変わりません。準備ができたらキーを追加するだけです。
dotnet add package Exis.PdfEditor
ご質問は?support@exisone.comにメール — ボットではなく開発者がお答えします。