比较

为 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 上运行。 由于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——据此规划容器资源。

许可:永久许可证起价为$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 库"的搜索结果中,与支持 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 美元。

功能对比

特征IronPDFiTextPDFSharpQuestPDFwkhtmltopdf木偶师亚斯
HTML 至 PDF满(铬)有限(CSS 2.1)已弃用完整版(Chrome)有限的
CSS Flexbox/网格
JavaScript有限的
Linux(无 libgdiplus)部分的*不适用
Docker部署标准 .NET 映像标准部分的*标准复杂复杂需要 libgdiplus
主动维护放弃
出版定价是的($2,998+)否(年收入 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秒<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版本需要学习特定领域的 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)和Puppeteer Sharp(MIT)都是免费且无条件的。QuestPDF在年收入低于 100 万美元时是免费的。iText需要遵守 AGPL 协议或获得商业许可(每年 1.5 万美元至 21 万美元)。 IronPDF的永久许可证起价为$2,998。 Aspose的起价约为$999。

在你做出决定之前

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

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

请注意Apryse, Aspose, DinkToPdf, NReco, PDFSharp, PuppeteerSharp, QuestPDF, iText和wkhtmltopdf是其各自所有者的注册商标。 本网站与Apryse, 亚斯 Pty Ltd, CodeFlint, DinkToPdf, NReco, PDFTron, PuppeteerSharp, empira Software GmbH,iTextGroup或wkhtmltopdf没有关联,也未获得其认可或赞助。 所有产品名称、徽标和品牌均为各自所有者的财产。 比较仅供参考,反映撰写时公开可用的信息。)}]