PORóWNANIE

HTML do PDF w C# — Rzeczywistość opcji bibliotek

Konwersja HTML do PDF w C# wymaga biblioteki, która faktycznie renderuje HTML – a nie takiej, która analizuje tylko podzbiór znaczników i aproksymuje CSS 2.1. Większość bibliotek polecanych w wątkach na Stack Overflow i dyskusjach na Reddit albo nie potrafi renderować nowoczesnego CSS, albo mają ograniczenia licencyjne dyskwalifikujące ich do użytku komercyjnego, albo zostały porzucone z niezałatanymi lukami bezpieczeństwa.

Ten artykuł porównuje biblioteki, które programiści faktycznie napotykają, szukając "HTML to PDF C#", dokumentuje, co każda z nich może a czego nie może renderować, zawiera testy wydajności z metodologią i pokazuje rzeczywisty koszt operacyjny każdej z metod.

Quickstart: HTML do 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")
$vbLabelText   $csharpLabel

Zainstaluj przez NuGet: Install-Package IronPDF. Działa na Windows, Linux, macOS i Docker bez zewnętrznych zależności.

Dłączego konwersja HTML do PDF jest trudna?

Poprawne renderowanie HTML do PDF wymaga zaimplementowania tych samych pięciu komponentów, co przeglądarka internetowa: parsera HTML, silnika CSS (w tym Flexbox, Grid, kaskadowania, specyficzności i zapytań medialnych), środowiska uruchomieniowego JavaScript, silnika układu i pipeline'u renderowania, który łączy to wszystko do PDF z dokładnością do sub-piksela.

Tradycyjne biblioteki PDF implementują pierwsze dwa komponenty częściowo i całkowicie pomijają JavaScript. Dlatego radzą sobie ze standardowym HTML, ale psują się na wszystkim, co nowoczesna przeglądarka renderuje poprawnie. Jedynym sposobem na dopasowanie się do wyjścia przeglądarki jest użycie silnika przeglądarki.

Które biblioteki faktycznie konwertują HTML do PDF?

Owijaczewkhtmltopdf— ekosystem błędów ładowania DLL

Najczęściej wyszukiwane zapytanie przyciągające programistów do tych artykułów to jakaś wariacja:

System.DllNotFoundException: Nie można załadować DLL 'libwkhtmltox'

Warianty specyficzne dla platformy obejmują:

Nie można załadować biblioteki współdzielonej 'wkhtmltox' lub jednej z jej zależności
    (Linux — libwkhtmltox.so not found)

Nie można odnaleźć określonego modułu. (0x8007007E)
    (Windows — wkhtmltox.dll path not configured)

dyld: Biblioteka nie załadowana: libwkhtmltox.dylib
    (macOS — not supported on ARM64/Apple Silicon)

Te błędy pochodzą z DinkToPdf, NReco.PdfGenerator, WkHtmlToXSharp i innych owijaczy C# wokół tej samej porzuconej binarki. Organizacjawkhtmltopdfna GitHub została zarchiwizowana w lipcu 2024. Podstawowy silnik QtWebKit został zdeprecjonowany przez Qt w 2015 roku. Strona statusu projektu wyraźnie oznacza go jako zdeprecjonowany.

Poza problemami z ładowaniem DLL, silnik renderujący jest zamrożony na poziomie możliwości około Safari 2011. Brak Flexbox, brak Grid, ograniczony CSS3, zawodny JavaScript. I są niezałatane krytyczne luki: CVE-2022-35583(CVSS 9.8) umożliwia ataki SSRF, które mogą wyciekają dane AWS przez spreparowany HTML.

Czaswkhtmltopdfjuż minął. Błędy ładowania DLL są symptomem głębszego problemu: polegasz na porzuconym oprogramowaniu bez drogi do przodu.

iText 7(pdfHTML Add-On) — Ograniczony CSS, Licencjonowany AGPL

Moduł pdfHTML iText konwertuje HTML do PDF, używając niestandardowego parsera — nie silnika przeglądarki. Obsługuje podstawowy HTML/CSS, ale nie renderuje Flexbox, Grid ani JavaScript.

Tryb awaryjny jest cichy: pdfHTML nie rzuca wyjątków, gdy napotyka nieobsługiwany CSS. Renderuje co może i ignoruje resztę. Kontener display: flex z gap: 20px i justify-content: space-between renderuje się jako pionowo ustawione elementy bez odstępu. Programiści odkrywają to po integracji, a nie w trakcie.

Licencjonowanie: AGPL — wymaga udostępnienia kodu źródłowego całej aplikacji dostępnej z sieci, lub zakupu licencji komercyjnej. Ceny nie są opublikowane; dane zewnętrzne sugerują $15,000–$210,000 rocznie.

Jak zużycie pamięci się porównuje?

pdfHTML iTexta ładuje cały dokument do pamięci w celu przetworzenia. Dla typowych dokumentów biznesowych jest to do opanowania, ale duże raporty HTML z osadzonymi obrazami mogą powodować znaczną presję pamięci w porównaniu do podejść strumieniowych.

DłączegoPdfSharpnie obsługuje HTML?

PdfSharp pojawia się w wynikach wyszukiwania "HTML to PDF" ze względu na swoją popularność (34.9 milionów pobrań z NuGet) i częste polecenia. Ale PdfSharp nie posiada parsera HTML. Dostarcza API rysowania oparte na współrzędnych: DrawString(), DrawRectangle(), DrawImage() z jednoznacznie określonymi pozycjami X/Y.

Często sugerowane obejście, HtmlRenderer.PdfSharp, obsługuje tylko HTML 4.01 i CSS Level 2. Jeśli Twój HTML używa jakiejkolwiek funkcji CSS wprowadzonej po 2010 roku — Flexbox (2012), Grid (2017), niestandardowe właściwości (2017), border-radius (2011) — nie będzie renderowany.

Programiści, którzy wybierają PdfSharp oczekując wsparcia dla HTML, albo kończą z ręcznym umieszczaniem każdego elementu przy użyciu kodu opartego na współrzędnych, albo dodają drugą bibliotekę do renderowania HTML — w tym momencie PdfSharp jest zbędny.

Co sprawia, że Puppeteer jest tak wymagający pod względem zasobów?

Puppeteer Sharp steruje przeglądarką Chrome w trybie headless za pośrednictwem powiązań .NET. Dokładność renderowania odpowiada Chrome, ponieważ jest to Chrome. Koszt jest operacyjny: zarządzasz zewnętrznymi procesami przeglądarki.

Oto jak faktycznie wygląda wdrożeniePuppeteer Sharpw środowisku produkcyjnym — nie jest to 5-wierszowy przykład z samouczków, ale kod puli przeglądarek potrzebny do równoczesnego generowania plików 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

To około 60 wierszy kodu infrastruktury, zanim wygenerujesz pojedynczy plik PDF. Potrzebne jest również monitorowanie wycieków pamięci (procesy Chromium z czasem gromadzą pamięć), kontrolę stanu oraz plik Dockerfile z ponad 20 zależnościami Chromium. Rozmiar obrazu Docker wzrasta o 300–400 MB.

Porównaj to z odpowiednim podejściem 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 jest opłacalny, jeśli Twój zespół jest w stanie pokryć związane z nim koszty operacyjne. Dla zespołów, które chcą skupić się na swojej aplikacji, a nie na infrastrukturze przeglądarki,IronPDFobsługuje to samo renderowanie wewnętrznie.

DłączegoQuestPDFnie może konwertować plików HTML?

QuestPDF pojawia się praktycznie w każdej dyskusji na temat "HTML do PDF w C#" na Reddicie i Stack Overflow. Powstaje w ten sposób spójny schemat: programiści kupują lub integrują QuestPDF, oczekując konwersji do HTML, a następnie odkrywają, że w ogóle nie renderuje on HTML.

QuestPDF to płynnie działający interfejs API w języku C# do programowego tworzenia dokumentów. Jego pozycjonowanie wyraźnie brzmi: "przestań zmagać się z konwersją HTML na PDF" — zastępuje ono podejście oparte na HTML kodem C#. Jest to świadomy wybór projektowy. Dyskusje na GitHubie z lat 2022–2024 pokazują, że programiści odkrywają to po rozpoczęciu wdrażania. Twórcy konsekwentnie potwierdzają, że obsługa HTML nie jest planowana.

Jeśli obecny proces wykorzystuje szablony HTML — widoki Razor dla faktur, kod HTML pulpitu nawigacyjnego dla raportów, treści internetowe do archiwizacji —QuestPDFwymaga przepisania każdego szablonu w płynnym kodzie API języka C#. W przypadku nowych projektów, w których tworzysz układy dokumentów od podstaw przy użyciu danych strukturalnych, APIQuestPDFjest dobrze zaprojektowane i wydajne.

Licencja społecznościowa obejmuje firmy o rocznych przychodach brutto poniżej 1 mln USD. Ponadto wymagańa jest licencja komercyjna.

A co z Aspose.PDF?

Aspose.PDF zapewnia szeroki zakres funkcji związanych z plikami PDF w ramach licencji komercyjnej (ceny zaczynają się od ~999 USD/programista). Konwersja HTML wykorzystuje niestandardowy silnik, a nie przeglądarkę — podobnie jak iText, obsługuje podstawowy HTML, ale nie renderuje poprawnie nowoczesnych funkcji CSS.

Głównym problemem jest stabilność platformy:Asposeopiera się na System.Drawing.Common, co wymaga libgdiplus w systemie Linux. W .NET 6+ firma Microsoft wycofała tę funkcję dla platform innych niż Windows. Programiści zgłaszają wycieki pamięci charakterystyczne dla wdrożeń w systemie Linux, które nie występują w systemie Windows. W środowiskach wyłącznie dla systemu Windows sprawdzi się Aspose. W przypadku wdrożeń wielopłatformowych lub kontenerowych łańcuch zależności stwarza ciągłe ryzyko.

W jaki sposóbIronPDFobsługuje konwersję HTML do PDF?

IronPDF osadza Chromium bezpośrednio w pakiecie NuGet. CSS Flexbox, Grid, niestandardowe właściwości, @font-face, zapytania medialne i JavaScript są wykonywane tak, jak w Chrome. Wynik jest zgodny z przeglądarką, ponieważ wykorzystuje ten sam silnik renderujący.

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

To używa CSS Grid z minmax, niestandardowymi właściwościami, linear-gradient, border-radius, i selektorami :root. Każda z tych funkcji nie działa w pdfHTML iText, rozbija się wwkhtmltopdfi nie istnieje w PdfSharp lub QuestPDF.

Jak przeprowadzić migrację z innych bibliotek?

Dla zespołów migrujących z iTextSharp lub wkhtmltopdf,IronPDFakceptuje bezpośrednio adresy URL — jest to przydatne, gdy istniejący przepływ pracy generuje pliki HTML lub wyświetla strony:

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

Wdrożenie

IronPDF działa na systemach Windows (x64), Linux (x64, ARM64), macOS (x64, Apple Silicon) oraz w kontenerach Docker. Konfiguracja Docker to standardowy obraz .NET Standard:

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]

Bez instalacji Chromium, bez zależności od bibliotek natywnych, bez konfiguracji piaskownicy.

Licencjonowanie: Licencje wieczyste już od 749 USD. Ceny opublikowane na stronie IronPDF.com. Bez licencji AGPL, bez opłat za dokument, bez progów przychodów.

Testy wydajności

Przetestowano na maszynie wirtualnej Azure Standard_D4s_v3 (4 vCPU, 16 GB pamięci RAM) z systemem Ubuntu 22.04 i platformą .NET 8. Dokument testowy: 200-elementowy szablon faktury HTML z układem CSS Grid, osadzonymi obrazami i wykresem wygenerowanym przez JavaScript. Każdy pomiar jest średnią z 50 iteracji po 5-iteracyjnym okresie rozgrzewki.

Scenariusz IronPDF Puppeteer Sharp iText pdfHTML wkhtmltopdf
Prosty HTML (bez JS) ~150ms ~500 ms ~200 ms ~200 ms
Złożone CSS (Flexbox/Grid) ~250ms ~600 ms Uszkodzony wynik Uszkodzony wynik
Treści renderowane w JavaScript ~350ms ~800ms Błędy (brak silnika JS) Nie powiodlo sie/Czesciowe
Pamieć na operacje ~80 MB ~150 MB ~60MB ~50MB
Zimny start (pierwsza generacja) 2–5s 3–8s <1s <1s

iText iwkhtmltopdfcharakteryzują się szybszym uruchamianiem, ponieważ nie inicjują silnika przeglądarki. Jednak porównanie to ma znaczenie tylko w scenariuszach, w których wszystkie biblioteki generują poprawny wynik — a w przypadku złożonej zawartości CSS lub JavaScript tylkoIronPDFiPuppeteer Sharpzapewniają użyteczne wyniki.

Uwaga: Są to typowe obserwacje dotyczące określonego sprzętu. Wydajność będzie zależała od złożoności kodu HTML, długości dokumentu oraz zasobów serwera. Przed podjęciem decyzji przetestuj rozwiązanie na rzeczywistych obciążeniach.

Porównanie funkcji

Funkcja IronPDF iText 7 Puppeteer Sharp wkhtmltopdf PdfSharp QuestPDF Aspose
HTML do PDF Tak (Chromium) Ograniczony (CSS 2.1) Tak (Chrome) Przestarzaly Nie Nie Ograniczone
CSS Flexbox/Grid Tak Nie Tak Nie Nie Nie Nie
Wykonanie kodu JavaScript Tak Nie Tak Ograniczone Nie Nie Nie
Wielopłatformowe (bez libgdiplus) Tak Tak Tak Nie dotyczy Częściowe Tak Nie
Opublikowane ceny $749+ Nie (od $15K–$210K/rok) Bezpłatne (MIT) Bezpłatne Bezpłatne (MIT) Bezpłatne <$1M $999+
Aktywna konserwacja Tak Tak Tak Porzucony Tak Tak Tak

Którą bibliotekę wybrać?

Szablony HTML z nowoczesnym CSS → IronPDF zapewnia wbudowany Chromium bez zewnętrznego zarządzania procesami. Jeśli Twój zespół potrafi zarządzać infrastrukturą przeglądarek,Puppeteer Sharpjest realną alternatywą.

Programowe generowanie dokumentów na podstawie danych, bez HTML → QuestPDF oferuje elegancki, płynny interfejs API. Nie wybieraj tego, oczekując konwersji HTML.

Prosta obróbka plików PDF (łączenie, dzielenie, znak wodny) → PdfSharp jest darmowy i nadaje się do zadań niezwiązanych z HTML.

Unikaj w nowych projektach:wkhtmltopdf(porzucony, CVE), iText bez licencji komercyjnej (pułapka AGPL),Asposena Linuksie (wycieki pamięci).

Kluczowe pytanie brzmi: czy w Twoim procesie pracy wykorzystujesz szablony HTML? W takim przypadku tylko rozwiązania oparte na Chromium generują poprawny wynik przy użyciu nowoczesnego CSS. Jeśli tak nie jest, wybór zależy od preferencji dotyczących API i ograniczeń licencyjnych.