比較

2026 年最佳 C# 文件產生庫選擇

選擇 C# PDF 函式庫會影響專案的授權風險、部署靈活性和長期維護成本。大多數在評估階段看似合適的函式庫,在實際生產環境中都會暴露出一些缺陷——例如意想不到的 AGPL 許可要求、與瀏覽器不匹配的 HTML 渲染效果,或者僅在 Linux 系統上才會出現的記憶體洩漏問題。

本文透過程式碼範例對主要選項進行了比較,記錄了實踐中重要的權衡取捨,並包含並排程式碼比較,使用三個不同的程式庫產生相同的發票,以便您可以直接看到 API 的差異。

快速入門:三行程式碼即可將 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")
$vbLabelText   $csharpLabel

無需額外配置,即可在 Windows、Linux、macOS 和 Docker 上運作。 由於IronPDF嵌入了相同的 Chromium 渲染引擎,因此輸出結果與 Chrome 一致。

評估標準

在比較不同圖書館之前,要先了解要評估哪些面向。 這些問題能及早發現生產問題:

標準測試什麼為什麼這很重要
HTML/CSS 渲染將你的實際模板(使用 Flexbox/Grid)導入其中。大多數程式庫聲稱支援 HTML,但實際上最多只能渲染 CSS 2.1。
JavaScript執行使用 Chart.js 或動態表格內容進行測試不支援JavaScript的函式庫會產生空白部分。
許可模式請閱讀完整授權協議,而非摘要。AGPL 要求開源您的整個應用程式
平台支援部署到目標 Linux/Docker/ARM64 環境Windows 的成功並不能預測 Linux 的行為。
記憶體負載循環產生 100 多個文檔單一文檔測試會隱藏導致生產伺服器崩潰的漏洞
出版價格請查看網站上是否標明價格。"接觸式銷售"通常指每年1.5萬美元至21萬美元的銷售額。

圖書館比較

IronPDF— 內建 Chromium,完全支援 CSS/JS

IronPDF 將 Chromium 直接嵌入 NuGet 套件中。 HTML渲染與Chrome一致,因為它使用的是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")
$vbLabelText   $csharpLabel

需要考慮的權衡:嵌入式 Chromium 會使部署包增加約 200MB。 對於標準伺服器和容器部署,這是一次性下載,不會對執行時間產生影響。 對於像 Azure Functions 消費計畫這樣規模受限的環境,請檢查部署規模限制。 冷啟動時,首次產生 PDF 檔案需要 2-5 秒進行 Chromium 初始化; 後續幾代運行時間為 100-500 毫秒。 記憶體基線約為 150-200MB-據此規劃容器資源。

許可:永久許可起價 749 美元(1 位開發者)。 價格已在ironpdf.com上公佈。 不收取單件費用,不收取 AGPL 費用,無收入門檻。

iText 7(iTextSharp) — AGPL 許可,有限 HTML

iText 是一個功能強大的 PDF 處理庫,擁有悠久的歷史。 pdfHTML 外掛程式提供 HTML 到 PDF 的轉換功能,但它不使用瀏覽器引擎——它使用自訂解析器來近似 CSS 2.1。

一家中型 SaaS 公司的生產團隊在將發票範本從 Razor 視圖遷移過來時發現了這個問題。 這些模板使用了 CSS Flexbox 來實現響應式列佈局。 整合 iText 的 pdfHTML 後,每張發票都以單列垂直堆疊的形式呈現。 display: flexgapjustify-content屬性被靜默忽略。 開發團隊花了三週才意識到 pdfHTML 無法渲染他們現有的 CSS。

AGPL 的實際情況: iText 使用 AGPL 授權。 如果您的應用程式可透過網路存取(包括所有 Web 應用程式、API 和 SaaS 產品),則必須根據 AGPL 發布您的整個應用程式原始程式碼。 不僅僅是PDF模組。 一切。 iText及其母公司Apryse積極執行這項規定。

商業許可: iText 於 2024 年過渡到訂閱式授權。價格未公開-請聯絡銷售部門以取得報價。 第三方數據顯示,每年費用為 15,000 美元至 210,000 美元,視使用量而定。

PdfSharp — 採用 MIT 許可證,不含 HTML

PdfSharp是真正免費的軟體,採用 MIT 許可證,NuGet 下載量達 3,490 萬次。 其缺點在於功能:它提供了一個基於座標的繪圖 API,但沒有 HTML 解析器、CSS 引擎和模板系統。

一個建立報表儀表板的團隊選擇了PdfSharp因為它免費且廣為人知。 他們花了四個月的時間編寫基於座標的佈局程式碼——計算每個文字元素的 X/Y 位置,逐像素繪製表格邊框,手動處理分頁符號。 當他們最終將自己的輸出與同一個 HTML 模板在瀏覽器中產生的輸出進行比較時,他們意識到他們建立的版本比基於 Chromium 的庫自動執行的操作還要糟糕。

PdfSharp非常適合合併 PDF、添加浮水印以及從資料建立簡單的結構化文件。 如果不需要 HTML 渲染,這仍然是一個合理的選擇。

QuestPDF— 簡潔的 API,無需 HTML,收入門檻

QuestPDF提供流暢的 C# API,以程式設計方式建立文件。 API 設計確實非常出色——它是所有類別中最好的 .NET 庫 API 之一。

有兩個限制條件:QuestPDF 不會渲染 HTML(這是設計使然——這是一個刻意的架構選擇,而不是缺少的功能),而且社群授權適用於年總收入低於 100 萬美元的企業。 一旦貴公司達到該門檻,商業許可就成為強制性要求。 即將達到臨界點的公司應該在情況變得緊急之前為此過渡做好預算。

儘管QuestPDF明確反對 HTML,但開發人員在開始實施後經常會發現這一點,因為該庫會出現在"C# PDF 庫"的搜尋結果中,與支援 HTML 的庫並列出現。

wkhtmltopdf封裝器 — 已棄用、未修補的 CVE

wkhtmltopdf 的時代已經過去了。 GitHub 組織已於 2024 年 7 月存檔。 底層 QtWebKit 引擎已於 2015 年被 Qt 棄用。已知的 CVE(包括CVE-2022-35583 (CVSS 9.8,SSRF 漏洞可導致 AWS 憑證外洩))將永遠不會被修補。

像 DinkToPdf、NReco.PdfGenerator 和 WkHtmlToXSharp 這樣的 C# 封裝器都封裝了同一個已棄用的二進位檔案。 渲染引擎的功能大約停留在 Safari 2011 的水平:沒有 Flexbox,沒有 Grid,JavaScript 功能有限。 對於新專案而言,這不是一個可行的選擇。

木偶師夏普 — 全面渲染,操作複雜

Puppeteer Sharp透過 .NET 綁定控制無頭 Chrome 瀏覽器。 渲染品質與 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
$vbLabelText   $csharpLabel

在生產環境中,您還需要瀏覽器進程池、記憶體洩漏監控(Chromium 進程可能會發生記憶體洩漏)、崩潰復原和資源清理。 Docker 部署需要安裝 Chromium 依賴項——與標準的 .NET 映像相比,Dockerfile 的內容相當龐大。 如果你的團隊能夠承擔營運成本,那麼 木偶師夏普 是一個可行的選擇。

Aspose.PDF — 功能豐富,但有 Linux 記憶體問題

Aspose.PDF 提供全面的 PDF 功能和完善的文件。 主要問題是 Linux 穩定性:Aspose 依賴 System.Drawing.Common,而 System.Drawing.Common 在 Linux 上需要 libgdiplus——這是一個已停止維護且有記憶體洩漏問題的函式庫。 開發商報告涵蓋數年:

"在 Unix 環境下,數十個請求會導致服務記憶體耗盡,但在 Windows 環境下不會發生這種情況。" — Aspose 論壇,2022 年 3 月

對於僅限 Windows 系統的部署,Aspose 仍然能夠勝任。 對於跨平台或容器化部署,System.Drawing.Common 依賴項會帶來持續的風險。 商業許可費用起價約為每位開發者 999 美元。

功能比較

特點IronPDFiText 7PdfSharpQuestPDFwkhtmltopdf木偶師亞斯
HTML 至 PDF滿(鉻)有限(CSS 2.1)已棄用完整版(Chrome)限額
CSS Flexbox/Grid
JavaScript限額
Linux(無 libgdiplus)部分的*不適用
Docker部署標準 .NET 映像標準部分的*標準複雜的複雜的需要 libgdiplus
主動維護
出版價格是的(749美元以上)否(年收入 1.5 萬美元至 21 萬美元)免費(MIT)是的(免費,低於100萬美元)自由的免費(MIT)是的(999美元以上)
永久授權無需訂閱不適用不適用不適用不適用
不包含AGPL協議否(需要商業用途)

*PdfSharp 記錄了某些配置下特定於平台的問題。

效能比較

在配置中等的雲端虛擬機器(4 個虛擬 CPU,8GB 記憶體)上使用包含 200 個元素的 HTML 發票範本進行測試,預熱後進行了 50 次迭代,取平均值:

工作場景IronPDF木偶師夏普iText pdfHTMLwkhtmltopdf
簡單的HTML頁面約150毫秒~500ms~200ms~200ms
複雜的CSS佈局(Flexbox/Grid)約250毫秒~600ms失敗/部分失敗約400毫秒(已損壞)
頁面大量使用了JavaScript約350毫秒約800毫秒失敗失敗/部分失敗
每次操作內存約80MB約150MB約60MB約50MB
冷啟動(第一代)2-5秒3–8秒<1秒<1秒

iText 和wkhtmltopdf的冷啟動速度更快,因為它們不需要初始化瀏覽器引擎——但它們也無法渲染相同的內容。 效能比較僅在所有函式庫都能產生正確輸出的情況下才有意義。

程式碼比較:同一張發票,三個庫

當建構同一個文件時,這些庫之間的差異最為明顯。 以下是三種不同方式產生的發票。

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 Class
$vbLabelText   $csharpLabel

QuestPDF— 流暢的 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 Class
$vbLabelText   $csharpLabel

PdfSharp-座標繪圖方法

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 Class
$vbLabelText   $csharpLabel

IronPDF 版本使用 HTML/CSS——這是大多數開發人員已經掌握的技能。QuestPDF版本需要學習特定領域的 Fluent API,但它提供了結構。 PdfSharp版本需要手動計算每個像素位置-每一列偏移、每一行高度、每一道單獨繪製的邊框。

我應該選擇哪個程式庫?

當我評估這些函式庫時,決策過程非常簡單明了:

需要使用現代 CSS 將 HTML 轉換為 PDF 嗎?IronPDF或 木偶師夏普 是比較實用的選擇。IronPDF內部支援 Chromium; 木偶師夏普 需要您管理外部瀏覽器進程。wkhtmltopdf不適用於新專案。 iText 的 pdfHTML 無法渲染 Flexbox 或 Grid。

想透過程式設計方式從資料建立文檔,而無需 HTML?QuestPDF的流暢 API 高效且設計精良。 PdfSharp提供更底層的控制,但實現相同的佈局需要編寫更多的程式碼。

跨平台部署(Linux、Docker、雲端)? IronPDF、QuestPDF 和 木偶師夏普 無需 libgdiplus 依賴即可在 Linux 上運行。 Aspose.PDF 已記錄到在 Linux 系統上有記憶體洩漏問題。 PdfSharp對平台的支援不完整,且有已知問題。

許可限制? PdfSharp (MIT) 和 木偶師夏普 (MIT) 都是免費的,沒有任何附加條件。QuestPDF在年收入低於 100 萬美元時是免費的。 iText 需要遵守 AGPL 協議或獲得商業許可(每年 1.5 萬美元至 21 萬美元)。IronPDF的永久許可證起價為 749 美元。 亞斯 的起價約為 999 美元。

在你做出決定之前

使用實際內容進行測試,而不是"Hello World"程式。儘早部署到目標平台。 測試記憶體佔用時,要測試 100 多個文檔,而不是單一文檔。 請與您的法律團隊一起閱讀完整的許可協議文本。 如果您的目標是無伺服器架構,請檢查冷啟動延遲。

IronPDF 提供功能齊全的試用版,可供您根據自身需求進行評估。