比較

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")
$vbLabelText   $csharpLabel

透過 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: flexgap: 20pxjustify-content: space-between容器,會被渲染成垂直堆疊的元素,彼此之間沒有間距。 開發人員是在整合之後才發現這個問題,而不是在整合過程中發現的。

許可: AGPL — 要求開源您的整個網路可存取應用程序,或購買商業許可。 價格未公佈; 第三方數據顯示,年收入為 15,000 美元至 210,000 美元。

記憶體使用情況比較如何?

iText 的 pdfHTML 功能會將整個文件載入到記憶體中進行處理。 對於一般的商業文件來說,這是可以控制的,但與串流方式相比,包含嵌入式影像的大型 HTML 報告可能會造成嚴重的記憶體壓力。

為什麼 PdfSharp 不支援 HTML?

PdfSharp因其受歡迎程度(3,490 萬 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
$vbLabelText   $csharpLabel

在產生單一 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 internally
using IronPdf;

var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
// Browser pooling, process management, crash recovery — handled internally
Imports IronPdf

Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(html)
' Browser pooling, process management, crash recovery — handled internally
$vbLabelText   $csharpLabel

如果你的團隊能夠承擔營運成本,那麼 木偶師夏普 是一個可行的選擇。 對於希望專注於應用程式而不是瀏覽器基礎架構的團隊來說,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")
$vbLabelText   $csharpLabel

它使用了 CSS Grid,並結合了auto-fit / minmax 、自訂屬性、 linear-gradientborder-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")
$vbLabelText   $csharpLabel

部署

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 pdfHTMLwkhtmltopdf
簡單的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 複雜性、文件長度和伺服器資源而異。 在做出決定之前,請使用實際工作負載進行測試。

功能比較

特點IronPDFiText 7木偶師夏普wkhtmltopdfPdfSharpQuestPDF亞斯
HTML 至 PDF是的(鉻)有限(CSS 2.1)是的(Chrome)已棄用限額
CSS Flexbox/Grid
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 偏好和授權限制。