为 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")无需额外配置,即可在 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")需要考虑的权衡:嵌入式 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: flex 、 gap和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 许可证,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在生产环境中,您还需要浏览器进程池、内存泄漏监控(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 美元。
功能对比
| 特征 | IronPDF | iText 7 | PdfSharp | QuestPDF | wkhtmltopdf | 木偶师 | 亚斯 |
|---|---|---|---|---|---|---|---|
| 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 pdfHTML | wkhtmltopdf |
|---|---|---|---|---|
| 简单的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 ClassQuestPDF— 流畅的 API 方法
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(40);
page.DefaultTextStyle(x => x.FontFamily("Segoe UI"));
page.Header()
.Text($"Invoice #{data.InvoiceNumber}")
.FontSize(24).FontColor(Colors.Blue.Darken2);
page.Content().Column(column =>
{
column.Item().Table(table =>
{
table.ColumnsDefinition(cols =>
{
cols.RelativeColumn(3);
cols.RelativeColumn(1);
cols.RelativeColumn(1);
});
table.Header(header =>
{
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Item").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Qty").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Price").FontColor(Colors.White);
});
foreach (var item in data.Items)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Name);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Quantity.ToString());
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text($"${item.Price:F2}");
}
});
column.Item().AlignRight().PaddingTop(20)
.Text($"Total: ${data.Total:F2}").FontSize(16).Bold();
});
});
});
using var stream = new MemoryStream();
document.GeneratePdf(stream);
return stream.ToArray();
}
}using QuestPDF.Fluent;
using QuestPDF.Infrastructure;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = Document.Create(container =>
{
container.Page(page =>
{
page.Size(PageSizes.A4);
page.Margin(40);
page.DefaultTextStyle(x => x.FontFamily("Segoe UI"));
page.Header()
.Text($"Invoice #{data.InvoiceNumber}")
.FontSize(24).FontColor(Colors.Blue.Darken2);
page.Content().Column(column =>
{
column.Item().Table(table =>
{
table.ColumnsDefinition(cols =>
{
cols.RelativeColumn(3);
cols.RelativeColumn(1);
cols.RelativeColumn(1);
});
table.Header(header =>
{
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Item").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Qty").FontColor(Colors.White);
header.Cell().Background(Colors.Blue.Medium).Padding(8)
.Text("Price").FontColor(Colors.White);
});
foreach (var item in data.Items)
{
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Name);
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text(item.Quantity.ToString());
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
.Padding(8).Text($"${item.Price:F2}");
}
});
column.Item().AlignRight().PaddingTop(20)
.Text($"Total: ${data.Total:F2}").FontSize(16).Bold();
});
});
});
using var stream = new MemoryStream();
document.GeneratePdf(stream);
return stream.ToArray();
}
}Imports QuestPDF.Fluent
Imports QuestPDF.Infrastructure
Imports System.IO
Public Class InvoiceGenerator
Public Function GenerateInvoice(data As InvoiceData) As Byte()
Dim document = Document.Create(Sub(container)
container.Page(Sub(page)
page.Size(PageSizes.A4)
page.Margin(40)
page.DefaultTextStyle(Function(x) x.FontFamily("Segoe UI"))
page.Header() _
.Text($"Invoice #{data.InvoiceNumber}") _
.FontSize(24).FontColor(Colors.Blue.Darken2)
page.Content().Column(Sub(column)
column.Item().Table(Sub(table)
table.ColumnsDefinition(Sub(cols)
cols.RelativeColumn(3)
cols.RelativeColumn(1)
cols.RelativeColumn(1)
End Sub)
table.Header(Sub(header)
header.Cell().Background(Colors.Blue.Medium).Padding(8) _
.Text("Item").FontColor(Colors.White)
header.Cell().Background(Colors.Blue.Medium).Padding(8) _
.Text("Qty").FontColor(Colors.White)
header.Cell().Background(Colors.Blue.Medium).Padding(8) _
.Text("Price").FontColor(Colors.White)
End Sub)
For Each item In data.Items
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
.Padding(8).Text(item.Name)
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
.Padding(8).Text(item.Quantity.ToString())
table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
.Padding(8).Text($"${item.Price:F2}")
Next
End Sub)
column.Item().AlignRight().PaddingTop(20) _
.Text($"Total: ${data.Total:F2}").FontSize(16).Bold()
End Sub)
End Sub)
End Sub)
Using stream As New MemoryStream()
document.GeneratePdf(stream)
Return stream.ToArray()
End Using
End Function
End ClassPdfSharp——坐标绘图方法
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
var titleFont = new XFont("Arial", 24);
var headerFont = new XFont("Arial", 12, XFontStyleEx.Bold);
var bodyFont = new XFont("Arial", 12);
double y = 40;
gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont,
XBrushes.DarkBlue, 40, y);
y += 50;
// Table header — manually positioned
double[] colX = { 40, 300, 400 };
double rowHeight = 30;
gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight);
gfx.DrawString("Item", headerFont, XBrushes.White, colX[0] + 10, y + 20);
gfx.DrawString("Qty", headerFont, XBrushes.White, colX[1] + 10, y + 20);
gfx.DrawString("Price", headerFont, XBrushes.White, colX[2] + 10, y + 20);
y += rowHeight;
// Each row drawn individually with explicit coordinates
foreach (var item in data.Items)
{
gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight);
gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX[0] + 10, y + 20);
gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX[1] + 10, y + 20);
gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX[2] + 10, y + 20);
y += rowHeight;
}
y += 20;
gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y);
using var stream = new MemoryStream();
document.Save(stream);
return stream.ToArray();
}
}using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;
public class InvoiceGenerator
{
public byte[] GenerateInvoice(InvoiceData data)
{
var document = new PdfDocument();
var page = document.AddPage();
var gfx = XGraphics.FromPdfPage(page);
var titleFont = new XFont("Arial", 24);
var headerFont = new XFont("Arial", 12, XFontStyleEx.Bold);
var bodyFont = new XFont("Arial", 12);
double y = 40;
gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont,
XBrushes.DarkBlue, 40, y);
y += 50;
// Table header — manually positioned
double[] colX = { 40, 300, 400 };
double rowHeight = 30;
gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight);
gfx.DrawString("Item", headerFont, XBrushes.White, colX[0] + 10, y + 20);
gfx.DrawString("Qty", headerFont, XBrushes.White, colX[1] + 10, y + 20);
gfx.DrawString("Price", headerFont, XBrushes.White, colX[2] + 10, y + 20);
y += rowHeight;
// Each row drawn individually with explicit coordinates
foreach (var item in data.Items)
{
gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight);
gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX[0] + 10, y + 20);
gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX[1] + 10, y + 20);
gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX[2] + 10, y + 20);
y += rowHeight;
}
y += 20;
gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y);
using var stream = new MemoryStream();
document.Save(stream);
return stream.ToArray();
}
}Imports PdfSharpCore.Drawing
Imports PdfSharpCore.Pdf
Imports System.IO
Public Class InvoiceGenerator
Public Function GenerateInvoice(data As InvoiceData) As Byte()
Dim document As New PdfDocument()
Dim page = document.AddPage()
Dim gfx = XGraphics.FromPdfPage(page)
Dim titleFont As New XFont("Arial", 24)
Dim headerFont As New XFont("Arial", 12, XFontStyleEx.Bold)
Dim bodyFont As New XFont("Arial", 12)
Dim y As Double = 40
gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont, XBrushes.DarkBlue, 40, y)
y += 50
' Table header — manually positioned
Dim colX As Double() = {40, 300, 400}
Dim rowHeight As Double = 30
gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight)
gfx.DrawString("Item", headerFont, XBrushes.White, colX(0) + 10, y + 20)
gfx.DrawString("Qty", headerFont, XBrushes.White, colX(1) + 10, y + 20)
gfx.DrawString("Price", headerFont, XBrushes.White, colX(2) + 10, y + 20)
y += rowHeight
' Each row drawn individually with explicit coordinates
For Each item In data.Items
gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight)
gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX(0) + 10, y + 20)
gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX(1) + 10, y + 20)
gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX(2) + 10, y + 20)
y += rowHeight
Next
y += 20
gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y)
Using stream As New MemoryStream()
document.Save(stream)
Return stream.ToArray()
End Using
End Function
End ClassIronPDF 版本使用 HTML/CSS——这是大多数开发人员已经掌握的技能。QuestPDF版本需要学习特定领域的 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 提供功能齐全的试用版,可供您根据自身需求进行评估。