2026年の文書生成に最適なC#ライブラリの選択
C# PDF ライブラリの選択は、プロジェクトのライセンス露出、デプロイの柔軟性、長期的なメンテナンスコストに影響します。評価時には適しているように見えたライブラリのほとんどは、実運用では限界があることがわかります。予想外のAGPL要件、ブラウザにマッチしないHTMLレンダリング、Linuxでのみ表面化するメモリリークなどです。
この記事では、主要なオプションをコード例で比較し、実際に重要なトレードオフを文書化し、APIの違いを直接確認できるように、3つの異なるライブラリ間で同じ請求書を生成するコードを並べて比較します。
クイックスタート:3行でHTMLからPDFへ
NuGet経由でインストールしてください:
IronPDFパッケージのインストールPDFを作成します:
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
pdf.SaveAs("output.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
pdf.SaveAs("output.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>")
pdf.SaveAs("output.pdf")これは、Windows、Linux、macOS、Docker上で追加設定なしで動作します。 IronPDFは同じChromeレンダリングエンジンを搭載しているため、出力はChromeにマッチします。
評価基準
ライブラリを比較する前に、何を評価すべきかを知ってください。 これらは、生産上の問題を早期に表面化させる質問です:
| 基準 | テスト内容 | 重要な理由 |
|---|---|---|
| HTML/CSSレンダリング | Flexbox/Gridを使用した実際のテンプレートを入力してください。 | ほとんどのライブラリはHTMLのサポートを謳っていますが、CSS 2.1のレンダリングがせいぜいです。 |
| JavaScriptの実行 | Chart.jsまたはダイナミックテーブルコンテンツを使用したテスト | JSをサポートしていないライブラリは、空白のセクションを生成します。 |
| ライセンスモデル | 要約ではなく、ライセンス全体を読む | AGPLでは、アプリケーション全体をオープンソースにする必要があります。 |
| プラットフォームサポート | ターゲットのLinux/Docker/ARM64環境にデプロイしてください。 | Windowsの成功はLinuxの動作を予測しない |
| 負荷のかかるメモリ | 100以上のドキュメントをループで生成 | 単一ドキュメントのテストで、本番サーバーをクラッシュさせるリークを隠す |
| 公開価格 | ウェブサイトに価格が記載されているか確認する | "コンタクトセールス"とは、年俸$15K-$210Kを意味することが多い。 |
ライブラリの比較
IronPDF- Chromiumを組み込み、CSS/JSをフルサポート
IronPdfはChromiumを直接NuGetパッケージに組み込みます。 HTMLのレンダリングは、Chromeのエンジンと一致します。CSS Flexbox、Grid、カスタムプロパティ、JavaScriptはすべて期待どおりに実行されます。
using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
var pdf = renderer.RenderHtmlAsPdf(@"
<html>
<head>
<style>
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 8px; padding: 20px; text-align: center; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><p>$1.2M</p></div>
<div class='card'><h3>Users</h3><p>45,230</p></div>
<div class='card'><h3>Uptime</h3><p>99.97%</p></div>
</div>
</body>
</html>");
pdf.SaveAs("dashboard.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;
var pdf = renderer.RenderHtmlAsPdf(@"
<html>
<head>
<style>
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 8px; padding: 20px; text-align: center; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><p>$1.2M</p></div>
<div class='card'><h3>Users</h3><p>45,230</p></div>
<div class='card'><h3>Uptime</h3><p>99.97%</p></div>
</div>
</body>
</html>");
pdf.SaveAs("dashboard.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print
Dim pdf = renderer.RenderHtmlAsPdf("
<html>
<head>
<style>
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 8px; padding: 20px; text-align: center; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><p>$1.2M</p></div>
<div class='card'><h3>Users</h3><p>45,230</p></div>
<div class='card'><h3>Uptime</h3><p>99.97%</p></div>
</div>
</body>
</html>")
pdf.SaveAs("dashboard.pdf")考慮すべきトレードオフ:埋め込まれたChromiumは、デプロイパッケージに~200MBを追加します。 標準的なサーバーとコンテナのデプロイメントでは、これは1回限りのダウンロードで、ランタイムへの影響はありません。 Azure Functionsの消費プランのようなサイズに制約のある環境では、デプロイのサイズ制限を確認してください。 コールドプロセスでの最初のPDF生成は、Chromiumの初期化に2~5秒かかります; 後続世代は100-500msで実行されます。 メモリのベースラインは~150~200MBで、それに応じてコンテナのリソースを計画してください。
ライセンス: 749ドルからの永続ライセンス(開発者1名)。 ironPdf.comで価格を公開しています。 ドキュメントごとの料金、AGPL、収益のしきい値はありません。
iText 7 (iTextSharp) - AGPL ライセンス、制限付き HTML
iTextは、長い歴史を持つ有能なPDF操作ライブラリです。 pdfHTMLアドオンはHTMLからPDFへの変換を提供しますが、ブラウザエンジンを使用しません。
ある中規模 SaaS 企業の制作チームは、請求書テンプレートを Razor のビューから移行したときに、このことに気づきました。 テンプレートは、レスポンシブなカラムレイアウトのためにCSS Flexboxを使用しました。 iTextのpdfHTMLを統合した後、すべての請求書は1列の縦書きのスタックとしてレンダリングされました。 display: flex, gap, justify-content プロパティは黙って無視されました。 pdfHTMLが既存のCSSをレンダリングできないことにチームが気づくまで、3週間の開発期間が費やされました。
AGPLの現実: iTextはAGPLライセンスを使用しています。 あなたのアプリケーションがネットワークにアクセス可能な場合(すべてのウェブアプリケーション、API、SaaS製品を含みます)、あなたはアプリケーション全体のソースコードをAGPLのもとで公開しなければなりません。 PDFモジュールだけではありません。 すべてです。 iTextと親会社のApryseは、これを積極的に実施しています。
商用ライセンス: iText は、2024年にサブスクリプションベースのライセンスに移行しました。価格は公表されていませんので、お見積もりは営業までお問い合わせください。 サードパーティのデータでは、使用量に応じて年間15,000~210,000ドルとなっています。
PdfSharp - MITライセンス、HTMLなし
PdfSharpは、3,490万件のNuGetダウンロードがあり、MITライセンスの下で純粋に無料です。 HTMLパーサーもCSSエンジンもテンプレート・システムもない座標ベースの描画APIを提供します。
レポーティングダッシュボードを構築しているチームがPdfSharpを選んだ理由は、無料で有名だったからです。 すべてのテキスト要素のX/Y位置を計算し、ピクセルごとにテーブルの境界線を描き、手作業で改ページを処理するなど、座標ベースのレイアウトコードを書くのに4カ月を費やしました。 最終的に、同じHTMLテンプレートがブラウザで出力するものと自分たちの出力を比較したとき、彼らはChromiumベースのライブラリが自動的に行うことのさらに悪いバージョンを構築していることに気づいた。
PdfSharpは、PDFを結合したり、透かしを追加したり、データから単純な構造化ドキュメントを構築したりするのに適しています。 HTMLレンダリングが不要な場合でも、正当な選択肢であることに変わりはありません。
QuestPDF - エレガントなAPI、HTMLなし、収益のしきい値
QuestPDFは、プログラムでドキュメントを構築するための流暢なC# APIを提供します。 APIのデザインは純粋に優れており、どのカテゴリーにおいても優れた.NETライブラリAPIの1つです。
2つの制約があります:QuestPDFはHTMLをレンダリングしません(設計上、これは意図的なアーキテクチャ上の選択であり、欠けている機能ではありません)。 あなたの会社がその閾値を超えると、商用ライセンスが必須となります。 移行が急務となる前に、そのための予算を確保する必要があります。
QuestPDFのHTMLに対する明確な位置づけにもかかわらず、このライブラリは"C# PDF ライブラリ"の検索結果にHTML対応ライブラリと並んで表示されるため、開発者は実装を開始した後にこのことを定期的に発見します。
wkhtmltopdf Wrappers - 見捨てられ、パッチが適用されていない CVE
wkhtmltopdfの時間は過ぎました。 GitHub 組織は 2024 年 7 月にアーカイブされました。 基礎となる QtWebKit エンジンは、2015 年に Qt によって廃止されました。CVE-2022-35583(CVSS9.8、SSRFによるAWSクレデンシャルの流出) を含む既知のCVEには、パッチが適用されません。
DinkToPdf、NReco.PdfGenerator、WkHtmlToXSharpなどのC#ラッパーは、すべて同じ放棄されたバイナリをラップします。 レンダリングエンジンは、Flexbox、Grid、制限付きJavaScriptなど、Safari 2011の機能程度で凍結されています。 これは、新しいプロジェクトには有効な選択肢ではありません。
シャープ- 完全なレンダリング、操作の複雑さ
Puppeteer Sharp は、.NETバインディングを介してヘッドレスChromeを制御します。 レンダリング品質は、is Chromeであるため、Chromeと一致します。 ダウンロード、プーリング、メモリモニタリング、クラッシュリカバリを含む外部ブラウザプロセスを管理します。
using PuppeteerSharp;
// Downloads ~280MB Chromium on first run
await new BrowserFetcher().DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(
new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
return await page.PdfAsync(new PdfOptions { Format = PaperFormat.A4, PrintBackground = true });using PuppeteerSharp;
// Downloads ~280MB Chromium on first run
await new BrowserFetcher().DownloadAsync();
await using var browser = await Puppeteer.LaunchAsync(
new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
return await page.PdfAsync(new PdfOptions { Format = PaperFormat.A4, PrintBackground = true });Imports PuppeteerSharp
' Downloads ~280MB Chromium on first run
Await (New BrowserFetcher()).DownloadAsync()
Await Using browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {.Headless = True})
Await Using page = Await browser.NewPageAsync()
Await page.SetContentAsync(html)
Return Await page.PdfAsync(New PdfOptions With {.Format = PaperFormat.A4, .PrintBackground = True})
End Using
End Using本番環境では、ブラウザのプロセスプール、メモリリーク監視(Chromiumプロセスはリークする可能性があります)、クラッシュリカバリ、リソースクリーンアップも必要です。Dockerのデプロイでは、Chromiumの依存関係をインストールする必要があり、標準的な.NETイメージと比較すると、かなりのDockerファイルが必要になります。パペッティアSharpは、チームが運用のオーバーヘッドを吸収できるのであれば、実行可能です。
Aspose.PDF-豊富な機能、Linux メモリの問題
Aspose.PDFは、優れたドキュメントとともに幅広いPDF機能を提供します。 重要な問題はLinuxの安定性です:AsposeはSystem.Drawing.Commonに依存しており、Linuxではlibgdiplusが必要です。libgdiplusはメンテナンスされていないライブラリで、メモリリークが文書化されています。 開発者レポートは数年にわたります:
Unix環境では数十回のリクエストでサービスがメモリ不足になるが、Windows環境では発生しない。 アスポーズ・フォーラム、2022年3月。
Windowsのみのデプロイメントでは、Asposeは可能です。 クロスプラットフォームやコンテナ化されたデプロイメントでは、System.Drawing.Commonの依存関係が継続的なリスクを生み出します。 商用ライセンスは、開発者1人あたり約999ドルからです。
機能比較
| フィーチャー | IronPDF | iText 7 | PdfSharp | QuestPDF | wkhtmltopdf | パペッティア | アスパス |
|---|---|---|---|---|---|---|---|
| HTMLからPDFへ | フル(Chromium) | 制限あり(CSS 2.1) | なし | なし | 非推奨 | フル(Chrome) | 制限的 |
| CSS フレックスボックス/グリッド | はい | なし | なし | なし | なし | はい | なし |
| JavaScript | はい | なし | なし | なし | 制限的 | はい | なし |
| Linux (libgdiplusなし) | はい | はい | 一部 | はい | 該当なし | はい | なし |
| Dockerデプロイメント | .NET 標準イメージ | 標準 | 一部 | 標準 | 複雑 | 複雑 | libgdiplus が必要です。 |
| アクティブメンテナンス | はい | はい | はい | はい | 中止 | はい | はい |
| 公開価格 | はい ($749+) | なし(年俸$15K-$210K) | 無料(MIT) | はい(無料 <$1M) | 無料 | 無料(MIT) | はい(999ドル以上) |
| 永久ライセンス | はい | いいえ(サブスクリプション) | 該当なし | 該当なし | 該当なし | 該当なし | はい |
| AGPLフリー | はい | いいえ(商用が必要です) | はい | はい | はい | はい | はい |
*PdfSharpは、いくつかの構成でプラットフォーム固有の問題を文書化しています。
パフォーマンス比較
中層クラウドVM(4 vCPU、8GB RAM)で、200要素のHTML請求書テンプレートを使用してテスト:
| シナリオ | IronPDF | シャープ | iText pdfHTML | wkhtmltopdf |
|---|---|---|---|---|
| シンプルなHTMLページ | ~150ms | ~500ms | ~200ms | ~200ms |
| 複雑なCSSレイアウト(Flexbox/Grid) | ~250ms | ~600ms | 失敗/部分的 | ~400ミリ秒(区切り) |
| JavaScriptを多用したページ | ~350ms | ~800ms | 失敗例 | 失敗/部分的 |
| 操作ごとのメモリ | ~80MB | ~150MB | ~60MB | ~50MB |
| コールドスタート(第一世代) | 2-5s | 3-8s | <1s | <1s |
iTextとwkhtmltopdfは、ブラウザエンジンを初期化しないため、コールドスタートが高速ですが、同じコンテンツをレンダリングすることもできません。 性能比較は、すべてのライブラリが正しい出力を生成するシナリオにのみ意味があります。
コードの比較:同じ請求書、3つのライブラリ
これらのライブラリの違いは、同じドキュメントを作成するときに最も明確になります。 以下は、3つの方法で作成された請求書です。
IronPDF-HTML/CSSアプローチ
using IronPdf;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var renderer = new ChromePdfRenderer();
string html = $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: 'Segoe UI', sans-serif; margin: 40px; }}
h1 {{ color: #2c3e50; }}
table {{ width: 100%; border-collapse: collapse; }}
th {{ background: #3498db; color: white; padding: 12px; text-align: left; }}
td {{ border-bottom: 1px solid #e0e0e0; padding: 10px; }}
.total {{ font-weight: bold; font-size: 1.2em; text-align: right; margin-top: 20px; }}
</style>
</head>
<body>
<h1>Invoice #{data.InvoiceNumber}</h1>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
{string.Join("", data.Items.Select(i =>
$"<tr><td>{i.Name}</td><td>{i.Quantity}</td><td>${i.Price:F2}</td></tr>"))}
</table>
<p class='total'>Total: ${data.Total:F2}</p>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
}using IronPdf;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var renderer = new ChromePdfRenderer();
string html = $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: 'Segoe UI', sans-serif; margin: 40px; }}
h1 {{ color: #2c3e50; }}
table {{ width: 100%; border-collapse: collapse; }}
th {{ background: #3498db; color: white; padding: 12px; text-align: left; }}
td {{ border-bottom: 1px solid #e0e0e0; padding: 10px; }}
.total {{ font-weight: bold; font-size: 1.2em; text-align: right; margin-top: 20px; }}
</style>
</head>
<body>
<h1>Invoice #{data.InvoiceNumber}</h1>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
{string.Join("", data.Items.Select(i =>
$"<tr><td>{i.Name}</td><td>{i.Quantity}</td><td>${i.Price:F2}</td></tr>"))}
</table>
<p class='total'>Total: ${data.Total:F2}</p>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
}Imports IronPdf
Public Class InvoiceGenerator
Public Function GenerateInvoice(data As InvoiceData) As Byte()
Dim renderer = New ChromePdfRenderer()
Dim html As String = $"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: 'Segoe UI', sans-serif; margin: 40px; }}
h1 {{ color: #2c3e50; }}
table {{ width: 100%; border-collapse: collapse; }}
th {{ background: #3498db; color: white; padding: 12px; text-align: left; }}
td {{ border-bottom: 1px solid #e0e0e0; padding: 10px; }}
.total {{ font-weight: bold; font-size: 1.2em; text-align: right; margin-top: 20px; }}
</style>
</head>
<body>
<h1>Invoice #{data.InvoiceNumber}</h1>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
{String.Join("", data.Items.Select(Function(i) $"<tr><td>{i.Name}</td><td>{i.Quantity}</td><td>${i.Price:F2}</td></tr>"))}
</table>
<p class='total'>Total: ${data.Total:F2}</p>
</body>
</html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
Return pdf.BinaryData
End Function
End ClassQuestPDF - 流暢なAPIアプローチ
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(40);
page.DefaultTextStyle(x => x.FontFamily("Segoe UI"));
page.Header()
.Text($"Invoice #{data.InvoiceNumber}")
.FontSize(24).FontColor(Colors.Blue.Darken2);
page.Content().Column(column =>
{
column.Item().Table(table =>
{
table.ColumnsDefinition(cols =>
{
cols.RelativeColumn(3);
cols.RelativeColumn(1);
cols.RelativeColumn(1);
});
table.Header(header =>
{
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Item").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Qty").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Price").FontColor(Colors.White);
});
foreach (var item in data.Items)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Name);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Quantity.ToString());
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text($"${item.Price:F2}");
}
});
column.Item().AlignRight().PaddingTop(20)
.Text($"Total: ${data.Total:F2}").FontSize(16).Bold();
});
});
});
using var stream = new MemoryStream();
document.GeneratePdf(stream);
return stream.ToArray();
}
}using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(40);
page.DefaultTextStyle(x => x.FontFamily("Segoe UI"));
page.Header()
.Text($"Invoice #{data.InvoiceNumber}")
.FontSize(24).FontColor(Colors.Blue.Darken2);
page.Content().Column(column =>
{
column.Item().Table(table =>
{
table.ColumnsDefinition(cols =>
{
cols.RelativeColumn(3);
cols.RelativeColumn(1);
cols.RelativeColumn(1);
});
table.Header(header =>
{
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Item").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Qty").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Price").FontColor(Colors.White);
});
foreach (var item in data.Items)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Name);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Quantity.ToString());
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text($"${item.Price:F2}");
}
});
column.Item().AlignRight().PaddingTop(20)
.Text($"Total: ${data.Total:F2}").FontSize(16).Bold();
});
});
});
using var stream = new MemoryStream();
document.GeneratePdf(stream);
return stream.ToArray();
}
}Imports QuestPDF.Fluent
Imports QuestPDF.Infrastructure
Imports System.IO
Public Class InvoiceGenerator
Public Function GenerateInvoice(data As InvoiceData) As Byte()
Dim document = Document.Create(Sub(container)
container.Page(Sub(page)
page.Size(PageSizes.A4)
page.Margin(40)
page.DefaultTextStyle(Function(x) x.FontFamily("Segoe UI"))
page.Header() _
.Text($"Invoice #{data.InvoiceNumber}") _
.FontSize(24).FontColor(Colors.Blue.Darken2)
page.Content().Column(Sub(column)
column.Item().Table(Sub(table)
table.ColumnsDefinition(Sub(cols)
cols.RelativeColumn(3)
cols.RelativeColumn(1)
cols.RelativeColumn(1)
End Sub)
table.Header(Sub(header)
header.Cell().Background(Colors.Blue.Medium).Padding(8) _
.Text("Item").FontColor(Colors.White)
header.Cell().Background(Colors.Blue.Medium).Padding(8) _
.Text("Qty").FontColor(Colors.White)
header.Cell().Background(Colors.Blue.Medium).Padding(8) _
.Text("Price").FontColor(Colors.White)
End Sub)
For Each item In data.Items
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
.Padding(8).Text(item.Name)
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
.Padding(8).Text(item.Quantity.ToString())
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
.Padding(8).Text($"${item.Price:F2}")
Next
End Sub)
column.Item().AlignRight().PaddingTop(20) _
.Text($"Total: ${data.Total:F2}").FontSize(16).Bold()
End Sub)
End Sub)
End Sub)
Using stream As New MemoryStream()
document.GeneratePdf(stream)
Return stream.ToArray()
End Using
End Function
End ClassPdfSharp - 座標描画のアプローチ
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
var titleFont = new XFont("Arial", 24);
var headerFont = new XFont("Arial", 12, XFontStyleEx.Bold);
var bodyFont = new XFont("Arial", 12);
double y = 40;
gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont,
XBrushes.DarkBlue, 40, y);
y += 50;
// Table header — manually positioned
double[] colX = { 40, 300, 400 };
double rowHeight = 30;
gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight);
gfx.DrawString("Item", headerFont, XBrushes.White, colX[0] + 10, y + 20);
gfx.DrawString("Qty", headerFont, XBrushes.White, colX[1] + 10, y + 20);
gfx.DrawString("Price", headerFont, XBrushes.White, colX[2] + 10, y + 20);
y += rowHeight;
// Each row drawn individually with explicit coordinates
foreach (var item in data.Items)
{
gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight);
gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX[0] + 10, y + 20);
gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX[1] + 10, y + 20);
gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX[2] + 10, y + 20);
y += rowHeight;
}
y += 20;
gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y);
using var stream = new MemoryStream();
document.Save(stream);
return stream.ToArray();
}
}using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
var titleFont = new XFont("Arial", 24);
var headerFont = new XFont("Arial", 12, XFontStyleEx.Bold);
var bodyFont = new XFont("Arial", 12);
double y = 40;
gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont,
XBrushes.DarkBlue, 40, y);
y += 50;
// Table header — manually positioned
double[] colX = { 40, 300, 400 };
double rowHeight = 30;
gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight);
gfx.DrawString("Item", headerFont, XBrushes.White, colX[0] + 10, y + 20);
gfx.DrawString("Qty", headerFont, XBrushes.White, colX[1] + 10, y + 20);
gfx.DrawString("Price", headerFont, XBrushes.White, colX[2] + 10, y + 20);
y += rowHeight;
// Each row drawn individually with explicit coordinates
foreach (var item in data.Items)
{
gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight);
gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX[0] + 10, y + 20);
gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX[1] + 10, y + 20);
gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX[2] + 10, y + 20);
y += rowHeight;
}
y += 20;
gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y);
using var stream = new MemoryStream();
document.Save(stream);
return stream.ToArray();
}
}Imports PdfSharpCore.Drawing
Imports PdfSharpCore.Pdf
Imports System.IO
Public Class InvoiceGenerator
Public Function GenerateInvoice(data As InvoiceData) As Byte()
Dim document As New PdfDocument()
Dim page = document.AddPage()
Dim gfx = XGraphics.FromPdfPage(page)
Dim titleFont As New XFont("Arial", 24)
Dim headerFont As New XFont("Arial", 12, XFontStyleEx.Bold)
Dim bodyFont As New XFont("Arial", 12)
Dim y As Double = 40
gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont, XBrushes.DarkBlue, 40, y)
y += 50
' Table header — manually positioned
Dim colX As Double() = {40, 300, 400}
Dim rowHeight As Double = 30
gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight)
gfx.DrawString("Item", headerFont, XBrushes.White, colX(0) + 10, y + 20)
gfx.DrawString("Qty", headerFont, XBrushes.White, colX(1) + 10, y + 20)
gfx.DrawString("Price", headerFont, XBrushes.White, colX(2) + 10, y + 20)
y += rowHeight
' Each row drawn individually with explicit coordinates
For Each item In data.Items
gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight)
gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX(0) + 10, y + 20)
gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX(1) + 10, y + 20)
gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX(2) + 10, y + 20)
y += rowHeight
Next
y += 20
gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y)
Using stream As New MemoryStream()
document.Save(stream)
Return stream.ToArray()
End Using
End Function
End ClassIronPdfバージョンはHTML/CSSを使用しています。 QuestPDFバージョンは、ドメイン固有の流暢なAPIを学習する必要がありますが、構造を提供します。 PdfSharpバージョンは、列のオフセット、行の高さ、個別に描画されるボーダーなど、すべてのピクセル位置を手動で計算する必要があります。
どのライブラリを選べばよいですか?
私がこれらのライブラリを評価するとき、決定木は簡単です:
モダンなCSSを使ったHTMLからPDFへの変換が必要ですか実用的な選択肢はIronPDFかPuppeteer Sharpです。IronPDFは内部的にChromiumを扱います;パペッティアSharpでは、外部ブラウザのプロセスを管理する必要があります。 wkhtmltopdfは新規プロジェクトのオプションではありません。 iTextのpdfHTMLはFlexboxやGridをレンダリングできません。
HTMLを使わずに、データからプログラムで文書を作成できますか? QuestPDFの流暢なAPIは生産的で、よく設計されています。 PdfSharpはより低レベルの制御を提供しますが、同等のレイアウトのためにかなり多くのコードを必要とします。
クロスプラットフォームデプロイメント(Linux、Docker、クラウド) IronPdf、QuestPDF、Puppeteer Sharpは、libgdiplusの依存なしにLinux上で動作します。 Aspose.PDFは、Linux上でのメモリリークを文書化しています。 PdfSharpは、既知の問題を含む一部のプラットフォームをサポートしています。
ライセンス制約 PdfSharp(MIT)とPuppeteer Sharp(MIT)は無条件で無料です。 QuestPDFは100万ドル以下の収益であれば無料です。 iTextでは、AGPL準拠または商用ライセンス(年間$15K~$210K)が必要です。 IronPdfの永久ライセンスは749ドルからです。Asposeは999ドルからです。
コミットする前に
Hello World "ではなく、実際のコンテンツでテストしてください。ターゲット・プラットフォームへのデプロイは早めに行いましょう。 1つではなく、100以上のドキュメントでメモリを測定してください。 法務チームと一緒にライセンス全文をお読みください。 サーバーレスをターゲットにしている場合は、コールドスタートのレイテンシーを確認してください。