用 C# 将 HTML 转换为 PDF - 库选项的现实意义
在 C# 中将 HTML 转换为 PDF 需要一个能够真正渲染 HTML 的库,而不是一个只能解析部分标签并近似 CSS 2.1 的库。Stack Overflow 帖子和 Reddit 讨论中推荐的大多数库要么无法渲染现代 CSS,要么存在使其无法用于商业用途的许可限制,要么已被弃用且存在未修复的安全漏洞。
本文比较了开发人员在搜索"HTML to PDF C#"时实际遇到的库,记录了每个库可以渲染和不能渲染的内容,包括性能基准测试和方法论,并展示了每种方法的实际运营成本。
快速入门:C# 中的 HTML 转 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")通过 NuGet 安装: Install-Package IronPDF 。 无需外部依赖即可部署到 Windows、Linux、macOS 和 Docker。
为什么HTML转PDF很困难?
将 HTML 正确渲染为 PDF 需要实现与 Web 浏览器相同的五个组件:HTML 解析器、CSS 引擎(包括 Flexbox、Grid、层叠、优先级和媒体查询)、JavaScript 运行时、布局引擎以及将所有这些内容以亚像素精度合成到 PDF 的渲染管道。
传统 PDF 库部分实现了前两个功能,完全跳过了 JavaScript。 这就是为什么它们可以处理简单的 HTML,但无法处理任何现代浏览器能够正确渲染的内容。 要想匹配浏览器输出,唯一的办法就是使用浏览器引擎。
哪些库可以将 HTML 转换为 PDF?
wkhtmltopdf封装器 — DLL 加载错误生态系统
开发者们最常搜索到这些文章的关键词是以下几种变体:
System.DllNotFoundException:无法加载 DLL"libwkhtmltox"平台特定变体包括:
无法加载共享库"wkhtmltox"或其某个依赖项
(Linux — libwkhtmltox.so not found)
找不到指定的模块。 (0x8007007E)
(Windows — wkhtmltox.dll path not configured)
dyld:库未加载:libwkhtmltox.dylib
(macOS — not supported on ARM64/Apple Silicon)这些错误来自 DinkToPdf、NReco.PdfGenerator、WkHtmlToXSharp 以及其他围绕同一个已废弃二进制文件的 C# 封装程序。 wkhtmltopdf GitHub 组织已于 2024 年 7 月存档。 QtWebKit 底层引擎已于 2015 年被 Qt 弃用。项目状态页面明确将其标记为已弃用。
除了 DLL 加载问题之外,渲染引擎的性能也仅停留在 Safari 2011 的水平。 没有 Flexbox,没有 Grid,CSS3 功能有限,JavaScript 不稳定。 此外,还有一些未修复的严重漏洞: CVE-2022-35583 (CVSS 9.8) 可利用 SSRF 攻击,通过精心构造的 HTML 窃取 AWS 凭证。
wkhtmltopdf 的时代已经过去了。 DLL 加载错误是更深层次问题的征兆:你依赖的软件已经过时,没有出路。
iText 7(pdfHTML 插件)— 有限的 CSS,AGPL 许可
iText 的 pdfHTML 模块使用自定义解析器(而非浏览器引擎)将 HTML 转换为 PDF。它可以处理基本的 HTML/CSS,但不会渲染 Flexbox、Grid 或 JavaScript。
故障模式是静默的:pdfHTML 在遇到不支持的 CSS 时不会抛出异常。 它只渲染能够渲染的部分,忽略其余部分。例如,一个设置了 `display: flex 、 gap: 20px和justify-content: space-between容器,会被渲染成垂直堆叠的元素,彼此之间没有间距。 开发人员是在集成之后才发现这个问题,而不是在集成过程中发现的。
许可: AGPL — 要求开源您的整个网络可访问应用程序,或购买商业许可。 价格未公布; 第三方数据显示,年收入为 15,000 美元至 210,000 美元。
内存使用情况对比如何?
iText 的 pdfHTML 功能会将整个文档加载到内存中进行处理。 对于一般的商业文档来说,这是可以控制的,但与流式传输方式相比,包含嵌入式图像的大型 HTML 报告可能会造成严重的内存压力。
为什么PdfSharp不支持 HTML?
PdfSharp因其受欢迎程度(3490 万 NuGet 下载量)和频繁推荐,出现在"HTML 转 PDF"的搜索结果中。 但是PdfSharp没有 HTML 解析器。 它提供基于坐标的绘图 API: DrawString() 、 DrawRectangle() 、 DrawImage()并带有明确的 X/Y 位置。
常用的解决方法HtmlRenderer.PdfSharp仅支持 HTML 4.01 和 CSS Level 2。 如果您的 HTML 使用了 2010 年之后引入的任何 CSS 功能——Flexbox(2012 年)、Grid(2017 年)、自定义属性(2017 年)、 border-radius (2011 年)——它将无法渲染。
选择PdfSharp并期望获得 HTML 支持的开发者最终要么需要使用基于坐标的代码手动定位每个元素,要么需要添加第二个库来进行 HTML 渲染——在这种情况下, PdfSharp就显得多余了。
是什么让《傀儡师》成为资源密集型游戏?
Puppeteer Sharp通过 .NET 绑定控制无头 Chrome 浏览器。 渲染精度与 Chrome 一致,因为它就是 Chrome。 成本在于运营方面:您需要管理外部浏览器进程。
以下是 木偶师夏普 生产环境的实际部署示例——不是教程中的 5 行示例代码,而是并发生成 PDF 所需的浏览器池代码:
using PuppeteerSharp;
using System.Collections.Concurrent;
public class PdfBrowserPool : IAsyncDisposable
{
private readonly ConcurrentBag<IBrowser> _available = new();
private readonly SemaphoreSlim _semaphore;
private readonly int _maxBrowsers;
public PdfBrowserPool(int maxBrowsers = 4)
{
_maxBrowsers = maxBrowsers;
_semaphore = new SemaphoreSlim(maxBrowsers, maxBrowsers);
}
public async Task InitializeAsync()
{
await new BrowserFetcher().DownloadAsync(); // ~280MB download
for (int i = 0; i < _maxBrowsers; i++)
{
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox",
"--disable-dev-shm-usage" }
});
_available.Add(browser);
}
}
public async Task<byte[]> ConvertHtmlToPdf(string html)
{
await _semaphore.WaitAsync();
IBrowser browser = null;
try
{
if (!_available.TryTake(out browser))
throw new InvalidOperationException("No browser available");
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
});
var result = await page.PdfAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true
});
return result;
}
catch (Exception ex) when (ex is NavigationException or TargetClosedException)
{
// Browser crashed — replace it
browser?.Dispose();
browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
});
throw; // Re-throw after recovery
}
finally
{
if (browser != null) _available.Add(browser);
_semaphore.Release();
}
}
public async ValueTask DisposeAsync()
{
foreach (var browser in _available)
{
await browser.CloseAsync();
browser.Dispose();
}
}
}using PuppeteerSharp;
using System.Collections.Concurrent;
public class PdfBrowserPool : IAsyncDisposable
{
private readonly ConcurrentBag<IBrowser> _available = new();
private readonly SemaphoreSlim _semaphore;
private readonly int _maxBrowsers;
public PdfBrowserPool(int maxBrowsers = 4)
{
_maxBrowsers = maxBrowsers;
_semaphore = new SemaphoreSlim(maxBrowsers, maxBrowsers);
}
public async Task InitializeAsync()
{
await new BrowserFetcher().DownloadAsync(); // ~280MB download
for (int i = 0; i < _maxBrowsers; i++)
{
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox",
"--disable-dev-shm-usage" }
});
_available.Add(browser);
}
}
public async Task<byte[]> ConvertHtmlToPdf(string html)
{
await _semaphore.WaitAsync();
IBrowser browser = null;
try
{
if (!_available.TryTake(out browser))
throw new InvalidOperationException("No browser available");
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
});
var result = await page.PdfAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true
});
return result;
}
catch (Exception ex) when (ex is NavigationException or TargetClosedException)
{
// Browser crashed — replace it
browser?.Dispose();
browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
});
throw; // Re-throw after recovery
}
finally
{
if (browser != null) _available.Add(browser);
_semaphore.Release();
}
}
public async ValueTask DisposeAsync()
{
foreach (var browser in _available)
{
await browser.CloseAsync();
browser.Dispose();
}
}
}Imports PuppeteerSharp
Imports System.Collections.Concurrent
Imports System.Threading
Public Class PdfBrowserPool
Implements IAsyncDisposable
Private ReadOnly _available As New ConcurrentBag(Of IBrowser)()
Private ReadOnly _semaphore As SemaphoreSlim
Private ReadOnly _maxBrowsers As Integer
Public Sub New(Optional maxBrowsers As Integer = 4)
_maxBrowsers = maxBrowsers
_semaphore = New SemaphoreSlim(maxBrowsers, maxBrowsers)
End Sub
Public Async Function InitializeAsync() As Task
Await (New BrowserFetcher()).DownloadAsync() ' ~280MB download
For i As Integer = 0 To _maxBrowsers - 1
Dim browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {
.Headless = True,
.Args = New String() {"--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"}
})
_available.Add(browser)
Next
End Function
Public Async Function ConvertHtmlToPdf(html As String) As Task(Of Byte())
Await _semaphore.WaitAsync()
Dim browser As IBrowser = Nothing
Try
If Not _available.TryTake(browser) Then
Throw New InvalidOperationException("No browser available")
End If
Await Using page = Await browser.NewPageAsync()
Await page.SetContentAsync(html, New NavigationOptions With {
.WaitUntil = New WaitUntilNavigation() {WaitUntilNavigation.Networkidle0}
})
Dim result = Await page.PdfAsync(New PdfOptions With {
.Format = PaperFormat.A4,
.PrintBackground = True
})
Return result
End Using
Catch ex As Exception When TypeOf ex Is NavigationException OrElse TypeOf ex Is TargetClosedException
' Browser crashed — replace it
browser?.Dispose()
browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {
.Headless = True,
.Args = New String() {"--no-sandbox", "--disable-setuid-sandbox"}
})
Throw ' Re-throw after recovery
Finally
If browser IsNot Nothing Then _available.Add(browser)
_semaphore.Release()
End Try
End Function
Public Async Function DisposeAsync() As ValueTask Implements IAsyncDisposable.DisposeAsync
For Each browser In _available
Await browser.CloseAsync()
browser.Dispose()
Next
End Function
End Class在生成单个 PDF 文件之前,需要编写大约 60 行基础架构代码。 您还需要内存泄漏监控(Chromium 进程会随着时间的推移积累内存)、健康检查以及包含 20 多个 Chromium 依赖项的 Dockerfile。 Docker 镜像大小增加 300-400MB。
将此与IronPDF的等效方法进行比较:
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
// Browser pooling, process management, crash recovery — handled internallyusing IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
// Browser pooling, process management, crash recovery — handled internallyImports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(html)
' Browser pooling, process management, crash recovery — handled internally如果你的团队能够承担运营成本,那么 木偶师夏普 是一个可行的选择。 对于希望专注于应用程序而不是浏览器基础架构的团队来说,IronPDF 在内部处理相同的渲染工作。
为什么QuestPDF无法转换 HTML?
在 Reddit 和 Stack Overflow 上几乎所有关于"HTML 转 PDF C#"的讨论中都会提到 QuestPDF。 这造成了一种一致的模式:开发者购买或集成QuestPDF时期望它能进行 HTML 转换,然后却发现它根本不渲染 HTML。
QuestPDF是一个流畅的 C# API,用于以编程方式创建文档。 它的定位明确是"停止与 HTML 到 PDF 的转换作斗争"——它用 C# 代码取代了 HTML 方法。 这是有意为之的设计选择。2022年至 2024 年的 GitHub 讨论显示,开发者是在开始实施后才发现这一点的。 维护人员始终确认暂无计划支持 HTML。
如果您的现有工作流程使用 HTML 模板(例如用于发票的 Razor 视图、用于报告的仪表板 HTML、用于归档的 Web 内容),QuestPDF 要求使用 C# 流式 API 代码重写每个模板。 对于使用结构化数据从头开始构建文档布局的新项目,QuestPDF 的 API 设计精良且高效。
社区许可证适用于年总收入低于 100 万美元的企业。 除此之外,还需要商业许可。
Aspose.PDF怎么样?
Aspose.PDF 提供广泛的 PDF 功能,采用商业许可模式(起价约为 999 美元/开发人员)。 HTML 转换使用自定义引擎,而不是浏览器——类似于 iText,它可以处理基本的 HTML,但不能准确地渲染现代 CSS 功能。
主要问题是平台稳定性:Aspose 依赖于 System.Drawing.Common,而 System.Drawing.Common 在 Linux 上需要 libgdiplus。 微软在 .NET 6+ 中弃用了对非 Windows 平台的此功能。 开发人员报告称,Linux 部署中存在特有的内存泄漏问题,而 Windows 上不会出现此类问题。 亚斯 能够满足仅限 Windows 环境的需求。 对于跨平台或容器化部署,依赖链会带来持续的风险。
IronPDF如何处理 HTML 到 PDF 的转换?
IronPDF 将 Chromium 直接嵌入到 NuGet 包中。 CSS Flexbox、Grid、自定义属性、 @font-face 、媒体查询和 JavaScript 的执行方式与在 Chrome 中相同。 输出结果与浏览器一致,因为它们使用了相同的渲染引擎。
using IronPdf;
var renderer = new ChromePdfRenderer();
string html = @"
<!DOCTYPE html>
<html>
<head>
<style>
:root { --primary: #2563eb; }
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
.card {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 12px; padding: 24px; text-align: center;
}
.card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
.card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
th { background: var(--primary); color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
<div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
<div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
</div>
<table>
<tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
<tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
<tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
</table>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
string html = @"
<!DOCTYPE html>
<html>
<head>
<style>
:root { --primary: #2563eb; }
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
.card {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 12px; padding: 24px; text-align: center;
}
.card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
.card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
th { background: var(--primary); color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
<div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
<div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
</div>
<table>
<tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
<tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
<tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
</table>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim html As String = "
<!DOCTYPE html>
<html>
<head>
<style>
:root { --primary: #2563eb; }
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
.card {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 12px; padding: 24px; text-align: center;
}
.card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
.card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
th { background: var(--primary); color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
<div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
<div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
</div>
<table>
<tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
<tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
<tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
</table>
</body>
</html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("report.pdf")它使用了 CSS Grid,并结合了auto-fit / minmax 、自定义属性、 linear-gradient 、 border-radius和:root选择器。 这些功能在 iText 的 pdfHTML 中都无法实现,在wkhtmltopdf中也无法实现,而且在PdfSharp或QuestPDF中根本不存在。
如何从其他库迁移?
对于从 iTextSharp 或wkhtmltopdf迁移过来的团队,IronPDF 直接接受 URL——当您现有的工作流程生成 HTML 文件或提供页面时,这非常有用:
using IronPdf;
var renderer = new ChromePdfRenderer();
// Convert from URL — useful when migrating fromwkhtmltopdfURL-based workflows
var pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly");
pdf.SaveAs("report.pdf");
// Convert from local HTML file
var pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html");
pdfFromFile.SaveAs("invoice.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
// Convert from URL — useful when migrating fromwkhtmltopdfURL-based workflows
var pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly");
pdf.SaveAs("report.pdf");
// Convert from local HTML file
var pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html");
pdfFromFile.SaveAs("invoice.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
' Convert from URL — useful when migrating from wkhtmltopdf URL-based workflows
Dim pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly")
pdf.SaveAs("report.pdf")
' Convert from local HTML file
Dim pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html")
pdfFromFile.SaveAs("invoice.pdf")部署
IronPDF 可在 Windows (x64)、Linux (x64, ARM64)、macOS (x64, Apple Silicon) 和 Docker 容器上运行。 Docker 配置是一个标准的 .NET 镜像:
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]无需安装 Chromium,无需依赖原生库,无需沙箱配置。
许可:永久许可起价 749 美元。价格信息请访问ironpdf.com 。 无AGPL协议,无单件收费,无收入门槛。
性能基准
在运行 Ubuntu 22.04 和 .NET 8 的 Standard_D4s_v3 Azure VM(4 个虚拟 CPU,16GB 内存)上进行了测试。测试文档:一个包含 200 个元素的 HTML 发票模板,采用 CSS Grid 布局、嵌入式图像和 JavaScript 生成的图表。 每次测量均取 50 次迭代的平均值,并在 5 次迭代的预热期后进行。
| 情景 | IronPDF | 木偶师夏普 | iText pdfHTML | wkhtmltopdf |
|---|---|---|---|---|
| 简单的HTML(不含JS) | 约150毫秒 | ~500ms | ~200ms | ~200ms |
| 复杂的 CSS(Flexbox/Grid) | 约250毫秒 | ~600ms | 输出损坏 | 输出损坏 |
| JavaScript渲染的内容 | 约350毫秒 | 约800毫秒 | 失败(无 JS 引擎) | 失败/部分失败 |
| 每次操作内存 | 约80MB | 约150MB | 约60MB | 约50MB |
| 冷启动(第一代) | 2-5秒 | 3–8秒 | <1秒 | <1秒 |
iText 和wkhtmltopdf的冷启动速度更快,因为它们无需初始化浏览器引擎。但这种比较仅在所有库都能生成正确输出的情况下才有意义——对于复杂的 CSS 或 JavaScript 内容,只有IronPDF和 木偶师夏普 能生成可用的结果。
注:这些是针对指定硬件的典型观察结果。 性能表现会因 HTML 复杂性、文档长度和服务器资源而异。 在做出决定之前,请使用实际工作负载进行测试。
功能对比
| 特征 | IronPDF | iText 7 | 木偶师夏普 | wkhtmltopdf | PdfSharp | QuestPDF | 亚斯 |
|---|---|---|---|---|---|---|---|
| HTML 至 PDF | 是的(铬) | 有限(CSS 2.1) | 是的(Chrome) | 已弃用 | 否 | 否 | 有限的 |
| CSS Flexbox/网格 | 是 | 否 | 是 | 否 | 否 | 否 | 否 |
| JavaScript 执行 | 是 | 否 | 是 | 有限的 | 否 | 否 | 否 |
| 跨平台(无需 libgdiplus) | 是 | 是 | 是 | 不适用 | 部分翻译 | 是 | 否 |
| 出版定价 | 749美元以上 | 否(年收入 1.5 万美元至 21 万美元) | 免费(MIT) | 免费 | 免费(MIT) | 免费 <100万美元 | 999美元以上 |
| 主动维护 | 是 | 是 | 是 | 放弃 | 是 | 是 | 是 |
我应该选择哪个库?
采用现代 CSS 的 HTML 模板 → IronPDF提供嵌入式 Chromium,无需外部进程管理。 如果您的团队能够管理浏览器基础架构,那么 木偶师夏普 是一个可行的替代方案。
通过数据进行程序化文档生成,无需 HTML → QuestPDF提供优雅流畅的 API。 不要指望它能转换成 HTML 格式。
简单的 PDF 操作(合并、拆分、添加水印)→ PdfSharp免费且功能强大,可处理非 HTML 任务。
新项目应避免使用: wkhtmltopdf(已弃用,存在 CVE 漏洞)、无商业许可的 iText(AGPL 陷阱)、Linux 上的 Aspose(内存泄漏)。
关键问题在于你的工作流程是否使用了 HTML 模板。 如果确实如此,只有基于 Chromium 的解决方案才能使用现代 CSS 生成正确的输出。 如果没有,则选择取决于 API 偏好和许可限制。