比較

C#中的HTML到PDF - 程式庫選項的現實情況

將HTML轉換為PDF在C#中需要一個實際呈現HTML的程式庫——而不是解析部分標籤並逼近CSS 2.1的方式。在Stack Overflow帖子和Reddit討論中推薦的大多數程式庫要么無法呈現現代CSS,要么因為許可限制而不適合商業用途,或者因安全漏洞未修補而被遺棄。

本文比較了開發者在搜索"HTML到PDF C#"時實際遇到的程式庫,記錄了每個程式庫可以和不可以呈現的內容,包括方法學的性能基準,並顯示了各種方法的實際運營成本。

Quickstart:HTML到PDFin C

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需要實現與網頁瀏覽器相同的五個組件:一個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(pdfHTML附加模組)——有限的CSS,AGPL許可

iText的pdfHTML模組使用自定義解析器——而非瀏覽器引擎,將HTML轉換為PDF。它處理基本的HTML/CSS但不能渲染Flexbox、Grid或JavaScript。

失敗模式是默默無聲的:當遇到不支持的CSS時pdfHTML不會拋出異常。 它可以渲染即可的內容,並忽略其餘內容。justify-content: space-between一起呈現為無間距的垂直堆疊元素。 開發者是在整合之後而不是在整合過程中發現這一點。

許可: AGPL——需要開源您的整個網絡可訪問應用程序,或購買商業許可。 價格未發布; 第三方數據顯示每年15,000–210,000美元。

內存使用比較怎麼樣?

iText的pdfHTML將整個文檔加載到內存中進行處理。 對於典型的商務文檔這是可控的,但包含嵌入圖像的大型HTML報告會導致相比流式方法更大的內存壓力。

為什麼PDFSharp不支持HTML?

PdfSharp出現在"HTML轉PDF"搜索結果中是因為其受歡迎程度(34.9百萬個NuGet下載)和頻繁的推薦。 但PdfSharp沒有HTML解析器。 它提供了一個基於坐標的繪圖API:DrawImage()具有明確的X/Y位置。

常見建議的變通方法HtmlRenderer.PdfSharp僅支持HTML 4.01和CSS等級2。 如果您的HTML使用了2010年後引入的任何CSS功能——Flexbox(2012)、Grid(2017)、自定義屬性(2017)、border-radius(2011)——它將不會呈現。

選擇PdfSharp是多餘的。

是什麼讓Puppeteer Sharp資源密集?

Puppeteer Sharp通過.NET綁定控制無頭Chrome。 渲染準確性與Chrome匹配,因為它就是Chrome。 成本則是運營的:您管理外部瀏覽器進程。

這就是生產Puppeteer Sharp部署的實際情況——而不是教程中的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

如果您的團隊能夠吸收運行開銷,Puppeteer Sharp是可行的。 對於希望專注於應用程式而不是瀏覽器基礎結構的團隊,IronPDF在內部處理同樣的渲染。

為何QuestPDF無法轉換HTML?

QuestPDF幾乎出現在每次在Reddit和Stack Overflow關於"HTML到PDF C#"的討論中。 這創造了一種一致模式:開發人員購買或整合QuestPDF希望是HTML轉換,然後發現它根本不渲染HTML。

QuestPDF是一個流暢的C# API,用於程序化文件創建。 其定位明確是"停止與HTML到PDF轉換鬥爭"——用C#代碼取代HTML方法。 這是有意的設計選擇。2022年至2024年的GitHub討論顯示開發人員在實施開始後發現這一點。 維護者一致確認不計畫支持HTML。

如果您的現有工作流程使用HTML範本——如發票的Razor視圖、報告的儀表板HTML、設檔用的網頁內容——QuestPDF需要將每個範本重寫為C#流暢API代碼。 對於新專案,您從頭開始使用結構化數據構建文檔佈局,QuestPDF的API設計精良且具生產力。

社區許可涵蓋年收入低於$100萬的企業。 超過這一點需要商業許可。

那Aspose.PDF呢?

Aspose.PDF提供廣泛的PDF功能,具有商業許可(起始價格約為每個開發人員$999) HTML轉換使用自定引擎而非瀏覽器——類似於iText,它處理基本的HTML但不能準確呈現現代CSS功能。

主要問題是平臺穩定性:Aspose依賴System.Drawing.Common,在Linux上需要libgdiplus。 微軟對.NET 6+的非Windows平臺停用了這個組件。 開發者報導特定於Linux部屬的內存洩漏,但在Windows上不會發生。 對於僅Windows環境,Aspose是有能力的。 對於跨平臺或容器化的部屬,依賴鏈會創造持續的風險。

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與:root選擇器。 這些功能都在iText的pdfHTML中失敗,在wkhtmltopdf中打破,且在PdfSharp或QuestPDF中不存在。

如何從其他程式庫遷移?

對於從iText或wkhtmltopdf遷移的團隊,IronPDF可直接接受URL——當您的現有工作流程生成HTML文件或服務頁面時非常有價值:

using IronPdf;

var renderer = new ChromePdfRenderer();

// Convert from URL — useful when migrating from wkhtmltopdf URL-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 from wkhtmltopdf URL-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安裝,沒有原生庫依賴,沒有沙箱配置。

許可:永久許可從$2,998開始。 公布價格位於ironpdf.com。 無AGPL,無每份文件費用,無收入門檻。

性能基準

在運行Ubuntu 22.04和.NET 8的Standard_D4s_v3 Azure虛擬機(4個vCPU,16GB RAM)上測試。測試文檔:200個元素HTML發票範本,帶有CSS Grid佈局、嵌入圖像和JavaScript生成的圖表。 每項測量在5次熱身後進行50次迭代的平均值。

情景IronPDFPuppeteer SharpiText pdfHTMLwkhtmltopdf
簡單HTML(無JS)~150ms~500ms~200ms~200ms
複雜CSS(Flexbox/Grid)~250ms~600ms壞掉的輸出壞掉的輸出
JavaScript 渲染內容~350ms~800ms未能(無JS引擎)未能/部分
每次操作的內存~80MB~150MB~60MB~50MB
冷啟動(首次生成)2–5秒3–8秒<1s<1s

iText和wkhtmltopdf顯示首次冷啟動較快,因為它們不初始化瀏覽器引擎。但這種比較僅在所有程式庫生成正確輸出場景中有意義——而對於複雜CSS或JavaScript內容,僅IronPDF和Puppeteer Sharp產生可用結果。

注意:這些代表在指定硬體上的典型觀察。 您的性能會隨著HTML複雜性、文檔長度和伺服器資源而有所不同。 在做決定之前,需使用您的實際工作負載進行測試。

功能比較

功能IronPDFiTextPuppeteer SharpwkhtmltopdfPDFSharpQuestPDFAspose
HTML到PDF是(Chromium)有限(CSS 2.1)是(Chrome)棄用沒有沒有有限
CSS Flexbox/Grid沒有沒有沒有沒有沒有
JavaScript執行沒有有限沒有沒有沒有
跨平台(無libgdiplus)不適用部分沒有
公布的價格$2,998+無($15K–$210K/年)免費(MIT)免費免費(MIT)免費<$1M$999+
積極維護被棄置

我應該選擇哪個程式庫?

具有現代CSS的HTML範本→ IronPDF 嵌入Chromium無需外部進程管理。 如果您的團隊可以管理瀏覽器基礎結構,Puppeteer Sharp是個可行的替代方案。

從數據生成程式化文檔,無HTML→ QuestPDF 提供一個優雅的流暢API。 不要選擇它以期待HTML轉換。

簡單的PDF操作(合併、拆分、水印)→ PDFSharp 是免費的,適用於非HTML任務。

避免用於新專案:wkhtmltopdf(廢棄的,CVE)、iText無商業授權(AGPL陷阱)、Linux上的Aspose(內存洩漏)。

關鍵問題是您的工作流程是否使用HTML範本。 如果是,只有基於Chromium的解決方案才能與現代CSS產生正確的輸出。 如果不是,選擇取決於API偏好和許可限制。

請注意Aspose、DinkToPdf、NReco、PDFSharp、PuppeteerSharp、QuestPDF、iText和wkhtmltopdf是各自所有者的註冊商標。 本網站與Aspose Pty Ltd、CodeFlint、DinkToPdf、NReco、PuppeteerSharp、empira Software GmbH、iText Group或wkhtmltopdf沒有關聯、認可或贊助關係。 所有產品名稱、標誌及商標均為其各自所有者的財產。 比較僅供信息參考,反映在寫作時公開的相關信息。)}]