比較

2026年文件生成最佳 C# 程式庫選擇

選擇C# PDF程式庫會影響專案的授權曝光、部署彈性和長期維護成本。在評估階段看似合適的程式庫,往往在生產中顯露出限制——您不曾預料到的AGPL要求、與瀏覽器不匹配的HTML渲染,或者只在Linux中顯現的記憶體洩漏。

本文比較了主要選項並提供代碼示例,記錄了實際中重要的取捨,並包括生成相同發票的三個不同程式庫的並列代碼比較,讓您能夠直接看到API的差異。

快速入門:三行代碼將HTML轉換為PDF

通過NuGet安裝:

Install-Package 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上運行。 輸出與Chrome匹配,因為IronPDF嵌入了相同的Chromium渲染引擎。

評估標準

在比較程式庫之前,了解需要評估什麼。 這些是早期暴露生產問題的問題:

標準測試內容為何重要
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直接在NuGet包中嵌入Chromium。 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–500ms之間運行。 內存基線是約150–200MB——請相應計劃容器資源。

授權:永久授權從$2,998起(1位開發者)。 在ironpdf.com上公布的定價。 沒有每份文檔費用,沒有AGPL,沒有收入門檻。

iText(iText)— AGPL授權,有限的HTML

iText是一個有長期歷史的功能強大的PDF操作程式庫。 pdfHTML附加組件提供HTML到PDF的轉換,但它不使用瀏覽器引擎——它通過自定義解析器來近似CSS 2.1。

一家中型SaaS公司中的生產團隊在將發票模板從Razor視圖遷移時發現了這一點。 模板使用了CSS Flexbox來進行響應式列佈局。 在整合iText的pdfHTML後,每個發票渲染為單列垂直堆疊。 justify-content屬性被悄然忽略。 三周的開發時間過去後,團隊才意識到pdfHTML無法渲染他們現有的CSS。

AGPL現實:iText使用AGPL授權。 如果您的應用程式是網絡可訪問的——這包括每個web應用、API和SaaS產品——您必須根據AGPL公開您的整個應用程式的源代碼。 不僅僅是PDF模塊。 是所有內容。 iText和母公司Apryse積極執行這一點。

商業授權:iText在2024年轉換為基於訂閱的授權。定價未公布——您需要聯繫銷售獲取報價。 第三方數據顯示,根據使用量,每年大約$15,000–$210,000。

PDFSharp— MIT授權,無HTML

PdfSharp在MIT授權下真的是免費的,擁有3490萬次NuGet下載。 取捨是功能:它提供基於座標的繪圖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 Library"搜索結果中出現在支持HTML的程式庫旁邊。

wkhtmltopdf包裝器 — 已放棄,未修補的CVE

wkhtmltopdf的時間已經過去。 GitHub組織在2024年7月被存檔。 底層的QtWebKit引擎在2015年被Qt棄用了。已知CVE——包括CVE-2022-35583(CVSS 9.8,SSRF可使AWS憑證外泄)——永遠不會被修復。

C#包裝器如DinkToPdf、NReco.PdfGenerator和WkHtmlToXSharp都包裝了相同的被放棄的二進制文件。 渲染引擎固定在約2011年Safari的功能:無Flexbox、無Grid、有限的JavaScript。 這對於新專案不是可行的選擇。

Puppeteer Sharp— 完整渲染,操作複雜性

Puppeteer Sharp通過.NET綁定控制Headless 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。 如果您的團隊能夠承擔操作開銷,Puppeteer Sharp是可行的。

Aspose.PDF — 功能廣泛,Linux內存問題

Aspose.PDF提供廣泛的PDF功能並有良好的文檔。 重大問題是Linux穩定性:Aspose依賴於System.Drawing.Common,這需要在Linux上使用libgdiplus——一個未維護的庫,記錄下來的內存洩漏。 開發者報告跨越多年的時間:

"在Unix環境中,幾十個請求讓服務內存耗盡,但這並沒有在基於Windows的環境中發生" — Aspose論壇,2022年3月

對於僅限Windows的部署,Aspose依然有效。 對於跨平台或容器化部署,System.Drawing.Common依賴性創造了持續的風險。 商業授權從每位開發者約999美元起。

功能比較

功能IronPDFiTextPDFSharpQuestPDFwkhtmltopdfPuppeteerAspose
HTML到PDF完整(Chromium)有限(CSS 2.1)已棄用完整(Chrome)有限
CSS Flexbox/Grid
JavaScript有限
Linux(無libgdiplus)部分*不適用
Docker部署標準.NET圖像標準部分*標準複雜複雜需要libgdiplus
積極維護已放棄
公布的定價是 ($2,998+)否($15K–$210K/年)免費(MIT)是(免費<$1M)免費免費(MIT)是($999+)
永久授權否(訂閱)不適用不適用不適用不適用
無AGPL否(需要商業)

*PDFSharp在某些配置中記錄了平台特定的問題。

性能比較

在中型雲VM(4 vCPU,8GB RAM)上測試,使用200元素HTML發票模板,在暖身後平均進行50次迭代:

場景IronPDFPuppeteer SharpiText pdfHTMLwkhtmltopdf
簡單HTML頁面~150ms~500ms~200ms~200ms
複雜的CSS佈局(Flexbox/Grid)~250ms~600ms失敗/部分~400ms(損壞)
JavaScript繁重頁面~350ms~800ms失敗失敗/部分
每次操作的內存~80MB~150MB~60MB~50MB
冷啟動(首次生成)2–5秒3–8秒<1s<1s

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版本需要學習一個特定領域的流暢API,但提供結構。 PdfSharp 版本要求手動計算每個像素位置——每個列的偏移量,每行的高度,逐個繪制邊框。

我應該選擇哪個程式庫?

當我評估這些程式庫時,決策樹很簡單:

需要現代CSS的HTML到PDF嗎?現實選擇是IronPDF或Puppeteer Sharp。IronPDF內部處理Chromium;PuppeteerSharp需要您管理外部瀏覽器過程。 wkhtmltopdf不是新專案的選擇。 iText的pdfHTML無法渲染Flexbox或Grid。

從數據編程生成文檔,無HTML? QuestPDF的流暢API富有成效且設計良好。 PdfSharp 提供較低層次的控制,但需要顯著更多代碼來實現等效佈局。

跨平台部署(Linux、Docker、雲)? IronPDF、QuestPDF和Puppeteer Sharp在Linux上運行,無libgdiplus依賴。 Aspose.PDF在Linux上有記錄的內存洩漏。 PdfSharp 提供部分平台支持,但存在已知問題。

授權限制? PdfSharp(MIT)和Puppeteer Sharp(MIT)免費且無條件。 QuestPDF在收入100萬美元以下免費。 iText需要符合AGPL或商業授權(每年$15K–$210K)。 IronPDF的永久授權從$2,998開始。 Aspose從約$999開始。

在您承諾之前

用您的實際內容進行測試,而不只是"Hello World"。盡早部署到目標平台。 測量不只一個,而是100多個文檔的內存。 與您的法律團隊一起閱讀完整的授權文本。 如果您的目標是無伺服器,請檢查冷啟動延遲。

IronPDF提供試用,具備完整功能以便針對您的具體需求進行評估。

請注意Apryse、Aspose、DinkToPdf、NReco、PDFSharp、PuppeteerSharp、QuestPDF、iText和wkhtmltopdf是其各自所有者的註冊商標。 本網站與Apryse、Aspose Pty Ltd、CodeFlint、DinkToPdf、NReco、PDFTron、PuppeteerSharp、empira Software GmbH、iText Group或wkhtmltopdf無關,未獲得其認可或贊助。 所有產品名稱、標誌和品牌均為其各自所有者的財產。 比較僅供信息之用,並反映撰寫時的公開信息。)}]