比较

为 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 下载量达 3490 万次。 其缺点在于功能:它提供了一个基于坐标的绘图 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/网格
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 美元。Aspose 的起价约为 999 美元。

在你做出决定之前

使用实际内容进行测试,而不是"Hello World"程序。尽早部署到目标平台。 测试内存占用时,要测试 100 多个文档,而不是单个文档。 请与您的法律团队一起阅读完整的许可协议文本。 如果您的目标是无服务器架构,请检查冷启动延迟。

IronPDF 提供功能齐全的试用版,可供您根据自身需求进行评估。