Tall Components與IronPDF:技術比較指南
在C#中將HTML轉換為PDF需要一個實際渲染HTML的程式庫,而不是僅解析標籤子集並近似CSS 2.1的程式庫。Stack Overflow和Reddit討論中推薦的大多數程式庫要麼無法渲染現代CSS,要麼有商業用途不相容的授權限制,或者已被廢棄且存在未修補的安全漏洞。
本文比較了開發者在"HTML到PDF C#"搜尋時實際遇到的程式庫,記錄每個程式庫可以或不能渲染的內容,包括帶有方法的效能基準,顯示每種方法的實際運行成本。
Quickstart: HTML到PDF in 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")透過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: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是多餘的。
Puppeteer Sharp為什麼資源密集?
Puppeteer Sharp通過.NET綁定控制Headless Chrome。 渲染精度匹配Chrome因為這就是Chrome。 成本在操作:您管理外部瀏覽器進程。
以下是生產Puppeteer Sharp部署的實際情況——不是教程中五行的範例,而是併發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如果您的團隊能夠吸收運行負擔,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。 微軟已不再為非Windows平台提供.NET 6+的支持。 開發者報告說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")這使用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")部署
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,無每份文件費用,無收入門檻。
效能基準
在標準的Standard_D4s_v3 Azure VM(4 vCPU,16GB RAM)上測試,運行於Ubuntu 22.04和 .NET 8。測試文件:含CSS Grid佈局、嵌入圖片以及JavaScript生成的圖表的200元素HTML發票模板。 每次測量在5次預熱後平均超過50次迭代。
| 場景 | IronPDF | Puppeteer Sharp | iText pdfHTML | wkhtmltopdf |
|---|---|---|---|---|
| 簡單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的複雜性、文件長度和伺服器資源而變化。 在做出決定之前用您實際的工作負載進行測試。
功能比較
| 功能 | IronPDF | iText | Puppeteer Sharp | wkhtmltopdf | PDFSharp | QuestPDF | Aspose |
|---|---|---|---|---|---|---|---|
| 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(已被放棄,CVEs),沒有商業授權的iText(AGPL陷阱),Linux上的Aspose(記憶體洩露)。
關鍵問題是您的工作流程是否使用HTML模板。 如果有,只有基於Chromium的解決方案才能生成帶有現代CSS的正確輸出。 如果不是,選擇取決於API偏好和授權限制。
[{i:(Aspose、DinkToPdf、NReco、PDFSharp、PuppeteerSharp、QuestPDF、iText、和wkhtmltopdf是其各自擁有者的註冊商標。 此網站與Aspose Pty Ltd、CodeFlint、DinkToPdf、NReco、PuppeteerSharp、empira Software GmbH、iText Group或wkhtmltopdf無關聯附屬、贊助或支持。 所有產品名稱、商標和品牌均為其各自所有者的財產。 比較僅供資訊參考,反映撰寫時公開可用的資訊。)
