PORóWNANIE

Wybór najlepszej biblioteki C# do generowania dokumentów w 2026

Wybor biblioteki PDF dla C# wpływa na ekspozycje licencyjna projektu, elastycznosc wdrożenia oraz dlugoterminowy koszt utrzymania. Większość bibliotek, które wydaja sie odpowiednie w trakcie oceny, ujawnia ograniczenia w produkcji — wymagania AGPL, którego sie nie spodziewales, renderowanie HTML niezgodne z przeglądarka lub wycieki pamięci, które wystepuja tylko na Linuxie.

Ten artykuł porównuje glówne opcje z przykładami kodu, dokumentuje kompromisy, które maja znaczenie w praktyce, oraz zawiera porównanie kodu generującego ten sam rachunek w trzech różnych bibliotekach, aby można bylo bezpośrednio zobaczyć różnice w API.

Szybki start: HTML do PDF w trzech liniach

Zainstaluj poprzez NuGet:

Install-Package IronPDF

Wygeneruj plik 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

Działa na systemach Windows, Linux, macOS i Docker bez dodatkowej konfiguracji. Wynik jest zgodny z przeglądarką Chrome, ponieważIronPDFwykorzystuje ten sam silnik renderujący Chromium.

Kryteria oceny

Zanim zaczniesz porównywać biblioteki, dowiedz się, co należy oceniać. Oto pytania, które pozwalają wcześnie wykryć problemy produkcyjne:

Kryteria Co należy przetestować Dłączego to ma znaczenie
Renderowanie HTML/CSS Przekaż jej swoje rzeczywiste szablony z Flexbox/Grid Większość bibliotek deklaruje obsługę HTML, ale w najlepszym razie renderuje CSS 2.1
Wykonanie koduJavaScript Test z wykorzystaniem Chart.js lub dynamicznej zawartości tabeli Biblioteki bez obsługi JS generują puste sekcje
Model licencyjny Zapoznaj się z pełną treścią licencji, a nie tylko z jej streszczeniem Licencja AGPL wymaga udostępnienia całej aplikacji na zasadach open source
Obsługa platform Wdrożenie w docelowym środowisku Linux/Docker/ARM64 Sukces systemu Windows nie pozwala przewidzieć zachowania systemu Linux
Pamięć pod obciążeniem Generuj ponad 100 dokumentów w pętli Testy na pojedynczych dokumentach ukrywają luki, które powodują awarie serwerów produkcyjnych
Opublikowane ceny Sprawdź, czy ceny są podane na stronie internetowej "Skontaktuj się z działem sprzedaży" często oznacza 15 000–210 000 USD rocznie

Porównanie bibliotek

IronPDF— wbudowany Chromium, pełna obsługa CSS/JS

IronPDF osadza Chromium bezpośrednio w pakiecie NuGet. Renderowanie HTML odpowiada Chrome, ponieważ jest to silnik Chrome. CSS Flexbox, Grid, właściwości niestandardowe iJavaScriptdziałają zgodnie z oczekiwaniami.

using IronPdf;

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;

var pdf = renderer.RenderHtmlAsPdf(@"
    <html>
    <head>
        <style>
            .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
            .card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
                    border-radius: 8px; padding: 20px; text-align: center; }
        </style>
    </head>
    <body>
        <div class='grid'>
            <div class='card'><h3>Revenue</h3><p>$1.2M</p></div>
            <div class='card'><h3>Users</h3><p>45,230</p></div>
            <div class='card'><h3>Uptime</h3><p>99.97%</p></div>
        </div>
    </body>
    </html>");
pdf.SaveAs("dashboard.pdf");
using IronPdf;

var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print;

var pdf = renderer.RenderHtmlAsPdf(@"
    <html>
    <head>
        <style>
            .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
            .card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
                    border-radius: 8px; padding: 20px; text-align: center; }
        </style>
    </head>
    <body>
        <div class='grid'>
            <div class='card'><h3>Revenue</h3><p>$1.2M</p></div>
            <div class='card'><h3>Users</h3><p>45,230</p></div>
            <div class='card'><h3>Uptime</h3><p>99.97%</p></div>
        </div>
    </body>
    </html>");
pdf.SaveAs("dashboard.pdf");
Imports IronPdf

Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.CssMediaType = IronPdf.Rendering.PdfCssMediaType.Print

Dim pdf = renderer.RenderHtmlAsPdf("
    <html>
    <head>
        <style>
            .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
            .card { background: linear-gradient(135deg, #f8fafc, #e2e8f0);
                    border-radius: 8px; padding: 20px; text-align: center; }
        </style>
    </head>
    <body>
        <div class='grid'>
            <div class='card'><h3>Revenue</h3><p>$1.2M</p></div>
            <div class='card'><h3>Users</h3><p>45,230</p></div>
            <div class='card'><h3>Uptime</h3><p>99.97%</p></div>
        </div>
    </body>
    </html>")
pdf.SaveAs("dashboard.pdf")
$vbLabelText   $csharpLabel

Kompromisy, które należy wziąć pod uwagę: Wbudowany Chromium zwiększa rozmiar pakietu instalacyjnego o około 200 MB. W przypadku standardowych wdrożeń na serwerach i w kontenerach jest to jednorazowe pobranie, które nie ma wpływu na działanie środowiska uruchomieniowego. W przypadku środowisk o ograniczonej wielkości, takich jak plan konsumpcyjny Azure Functions, należy sprawdzić limit wielkości wdrożenia. Pierwsze wygenerowanie pliku PDF w procesie na zimno zajmuje 2–5 sekund na inicjalizację Chromium; kolejne generacje działają w czasie 100–500 ms. Wymagania pamięciowe wynoszą około 150–200 MB — należy odpowiednio zaplanować zasoby kontenera.

Licencjonowanie: Licencje wieczyste od 749 USD (1 programista). Ceny opublikowane na stronie IronPDF.com. Brak opłat za dokument, brak licencji AGPL, brak progów przychodów.

iText 7(iTextSharp) — Licencja AGPL, ograniczony HTML

iText to wydajna biblioteka do obsługi plików PDF o długiej historii. Dodatek pdfHTML umożliwia konwersję HTML do PDF, ale nie korzysta z silnika przeglądarki — aproksymuje CSS 2.1 za pomocą niestandardowego parsera.

Zespół produkcyjny w średniej wielkości firmie SaaS odkrył to podczas migracji szablonów faktur z widoków Razor. Szablony wykorzystują CSS Flexbox do tworzenia responsywnych układów kolumn. Po zintegrowaniu biblioteki pdfHTML firmy iText wszystkie faktury były renderowane jako pionowy stos w jednym kolumnie. Właściwości display: flex, gap oraz justify-content zostaly cicho zignorowane. Zespół poświęcił trzy tygodnie na prace programistyczne, zanim zdał sobie sprawę, że pdfHTML nie jest w stanie renderować ich istniejącego CSS.

Rzeczywistość AGPL: iText korzysta z licencji AGPL. Jeśli Twoja aplikacja jest dostępna w sieci — co obejmuje każdą aplikację internetową, API i produkt SaaS — musisz udostępnić cały kod źródłowy aplikacji na licencji AGPL. Nie tylko moduł PDF. Wszystko. iText i firma macierzysta Apryse aktywnie dbają o to.

Licencjonowanie komercyjne: w 2024 r. firma iText przeszła na model licencjonowania oparty na subskrypcji. Ceny nie są publikowane — w celu uzyskania wyceny należy skontaktować się z działem sprzedaży. Dane zewnętrzne wskazują na kwotę od 15 000 do 210 000 dolarów rocznie, w zależności od intensywności użytkowania.

PdfSharp— licencja MIT, bez HTML

PdfSharp jest naprawde darmowe na licencji MIT z 34,9 mln pobran z NuGet. Kompromisem jest funkcjonalność: zapewnia API rysowania oparte na współrzędnych, bez parsera HTML, silnika CSS i systemu szablonów.

Zespol budujacy dashboard raportujacy wybral PdfSharp, poniewaz bylo darmowe i dobrze znane. Spędzili cztery miesiące na pisaniu kodu układu opartego na współrzędnych — obliczając pozycje X/Y dla każdego elementu tekstowego, rysując obramowania tabel piksel po pikselu, ręcznie obsługując podziały stron. Kiedy w końcu porównali swój wynik z tym, co ten sam szablon HTML wygenerował w przeglądarce, zdali sobie sprawę, że stworzyli gorszą wersję tego, co biblioteka oparta na Chromium robi automatycznie.

PdfSharp dobrze działa przy łączeniu PDF, dodawaniu znaków wodnych oraz tworzeniu prostych dokumentów strukturalnych z danych. Jeśli nie potrzebujesz renderowania HTML, pozostaje to uzasadnioną opcją.

QuestPDF— Eleganckie API, bez HTML, próg przychodów

QuestPDF oferuje płynny interfejs API w języku C# do programowego tworzenia dokumentów. Projekt API jest naprawdę dobry — to jedno z lepszych API bibliotek .NET w każdej kategorii.

Istotne są dwa ograniczenia:QuestPDFnie renderuje HTML (z założenia — jest to świadomy wybór architektoniczny, a nie brakująca funkcja), a licencja Community License obejmuje firmy o rocznych przychodach brutto poniżej 1 mln USD. Gdy Twoja firma przekroczy ten próg, licencja komercyjna staje się obowiązkowa. Firmy zbliżające się do tego punktu zwrotnego powinny przeznaczyć środki na tę transformację, zanim stanie się ona pilna.

Pomimo jasnego pozycjonowaniaQuestPDFw opozycji do HTML, programiści często odkrywają to dopiero po rozpoczęciu wdrażania, ponieważ biblioteka pojawia się w wynikach wyszukiwania "biblioteka PDF dla C#" obok bibliotek obsługujących HTML.

Nadrzędkiwkhtmltopdf— porzucone, niezałatane luki CVE

Czaswkhtmltopdfjuż minął. Organizacja GitHub została zarchiwizowana w lipcu 2024 r. Podstawowy silnik QtWebKit został wycofany przez Qt w 2015 roku. Znane luki CVE — w tym CVE-2022-35583 (CVSS 9.8, umożliwiająca wyciek danych uwierzytelniających AWS poprzez SSRF) — nigdy nie zostaną załatane.

Narzędzia typu wrapper dla języka C#, takie jak DinkToPdf, NReco.PdfGenerator i WkHtmlToXSharp, wykorzystują ten sam, już nieaktualny plik binarny. Silnik renderujący jest zamrożony na poziomie możliwości Safari z około 2011 roku: bez Flexbox, bez Grid, z ograniczonym JavaScriptem. Nie jest to opcja odpowiednia dla nowych projektów.

Puppeteer Sharp— pełne renderowanie, złożoność operacyjna

Puppeteer Sharp steruje przeglądarką Chrome w trybie headless za pośrednictwem powiązań .NET. Jakość renderowania odpowiada Chrome, ponieważ jest to Chrome. Kompromis ma charakter operacyjny: zarządzasz zewnętrznymi procesami przeglądarki, w tym pobieraniem, pulą procesów, monitorowaniem pamięci i odzyskiwaniem po awarii.

using PuppeteerSharp;

// Downloads ~280MB Chromium on first run
await new BrowserFetcher().DownloadAsync();

await using var browser = await Puppeteer.LaunchAsync(
    new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
return await page.PdfAsync(new PdfOptions { Format = PaperFormat.A4, PrintBackground = true });
using PuppeteerSharp;

// Downloads ~280MB Chromium on first run
await new BrowserFetcher().DownloadAsync();

await using var browser = await Puppeteer.LaunchAsync(
    new LaunchOptions { Headless = true });
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html);
return await page.PdfAsync(new PdfOptions { Format = PaperFormat.A4, PrintBackground = true });
Imports PuppeteerSharp

' Downloads ~280MB Chromium on first run
Await (New BrowserFetcher()).DownloadAsync()

Await Using browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {.Headless = True})
    Await Using page = Await browser.NewPageAsync()
        Await page.SetContentAsync(html)
        Return Await page.PdfAsync(New PdfOptions With {.Format = PaperFormat.A4, .PrintBackground = True})
    End Using
End Using
$vbLabelText   $csharpLabel

W środowisku produkcyjnym potrzebne są również puli procesów przeglądarki, monitorowanie wycieków pamięci (procesy Chromium mogą powodować wycieki), odzyskiwanie po awarii oraz czyszczenie zasobów. Wdrożenie w Dockerze wymaga zainstalowania zależności Chromium — co oznacza znaczny plik Dockerfile w porównaniu ze standardowym obrazem .NET Standard.Puppeteer Sharpjest opłacalny, jeśli Twój zespół jest w stanie pokryć związane z nim koszty operacyjne.

Aspose.PDF — bogate funkcje, problemy z pamięcią w systemie Linux

Aspose.PDF oferuje szeroki zakres funkcji związanych z plikami PDF wraz z dobrą dokumentacją. Istotną kwestią jest stabilność systemu Linux:Asposeopiera się na bibliotece System.Drawing.Common, która w systemie Linux wymaga biblioteki libgdiplus — biblioteki, która nie jest już utrzymywana i w której odnotowano wycieki pamięci. Raporty programistów obejmują lata:

"Kilkadziesiąt żądań powoduje wyczerpanie pamięci usługi w środowisku Unix, ale nie ma to miejsca w środowisku opartym na systemie Windows" — Forum Aspose, marzec 2022 r.

W przypadku wdrożeń wyłącznie w systemie WindowsAsposenadal spełnia swoje zadanie. W przypadku wdrożeń wielopłatformowych lub kontenerowych zależność od System.Drawing.Common stwarza stałe ryzyko. Ceny licencji komercyjnych zaczynają się od około 999 USD na programistę.

Porównanie funkcji

Funkcja IronPDF iText 7 PdfSharp QuestPDF wkhtmltopdf Puppeteer Aspose
HTML do PDF Pełny (Chromium) Ograniczony (CSS 2.1) Nie Nie Przestarzaly Pełny (Chrome) Ograniczone
CSS Flexbox/Grid Tak Nie Nie Nie Nie Tak Nie
JavaScript Tak Nie Nie Nie Ograniczone Tak Nie
Linux (bez libgdiplus) Tak Tak Czesciowy* Tak Nie dotyczy Tak Nie
Wdrożenie Docker Standardowy obraz .NET Standardowy Czesciowy* Standardowy Złożone Złożone Wymaga libgdiplus
Aktywna konserwacja Tak Tak Tak Tak Porzucony Tak Tak
Opublikowane ceny Tak (od $749+) Nie (od $15K–$210K/rok) Bezpłatne (MIT) Tak (darmowy do $1M) Bezpłatne Bezpłatne (MIT) Tak (od $999+)
Licencja wieczysta Tak Nie (subskrypcja) Nie dotyczy Nie dotyczy Nie dotyczy Nie dotyczy Tak
Bez AGPL Tak Nie (wymaga komercyjnej) Tak Tak Tak Tak Tak

*PdfSharp dokumentuje problemy specyficzne dla platformy z niektorymi konfiguracjami.

Porównanie wydajności

Testowano na średniej klasy VM w chmurze (4 vCPU, 8GB RAM) z 200-elementowym szablonem faktury HTML, średnio po 50 iteracjach po rozgrzaniu:

Scenariusz IronPDF Puppeteer Sharp iText pdfHTML wkhtmltopdf
Prosta strona HTML ~150ms ~500 ms ~200 ms ~200 ms
Złożony układ CSS (Flexbox/Grid) ~250ms ~600 ms Nie powiodlo sie/Czesciowe ~400ms (uszkodzony)
Strona silnie bazujaca na JavaScript ~350ms ~800ms Nie powiodlo sie Nie powiodlo sie/Czesciowe
Pamieć na operacje ~80 MB ~150 MB ~60MB ~50MB
Zimny start (pierwsza generacja) 2–5s 3–8s <1s <1s

iText orazwkhtmltopdfwykazuja szybsze zimne starty, poniewaz nie inicjalizuja silnika przeglądarki — ale tez nie mogą renderować tej samej zawartości. Porównanie wydajności ma sens tylko w scenariuszach, gdzie wszystkie biblioteki produkuja poprawny wynik.

Porównanie kodu: ta sama faktura, trzy biblioteki

Roznice miedzy tymi bibliotekami są najjasniejsze, gdy buduje sie ten sam dokument. Oto faktura generowana na trzy sposoby.

IronPDF— Podejście HTML/CSS

using IronPdf;

public class InvoiceGenerator
{
    public byte[] GenerateInvoice(InvoiceData data)
    {
        var renderer = new ChromePdfRenderer();

        string html = $@"
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body {{ font-family: 'Segoe UI', sans-serif; margin: 40px; }}
                h1 {{ color: #2c3e50; }}
                table {{ width: 100%; border-collapse: collapse; }}
                th {{ background: #3498db; color: white; padding: 12px; text-align: left; }}
                td {{ border-bottom: 1px solid #e0e0e0; padding: 10px; }}
                .total {{ font-weight: bold; font-size: 1.2em; text-align: right; margin-top: 20px; }}
            </style>
        </head>
        <body>
            <h1>Invoice #{data.InvoiceNumber}</h1>
            <table>
                <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
                {string.Join("", data.Items.Select(i =>
                    $"<tr><td>{i.Name}</td><td>{i.Quantity}</td><td>${i.Price:F2}</td></tr>"))}
            </table>
            <p class='total'>Total: ${data.Total:F2}</p>
        </body>
        </html>";

        var pdf = renderer.RenderHtmlAsPdf(html);
        return pdf.BinaryData;
    }
}
using IronPdf;

public class InvoiceGenerator
{
    public byte[] GenerateInvoice(InvoiceData data)
    {
        var renderer = new ChromePdfRenderer();

        string html = $@"
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body {{ font-family: 'Segoe UI', sans-serif; margin: 40px; }}
                h1 {{ color: #2c3e50; }}
                table {{ width: 100%; border-collapse: collapse; }}
                th {{ background: #3498db; color: white; padding: 12px; text-align: left; }}
                td {{ border-bottom: 1px solid #e0e0e0; padding: 10px; }}
                .total {{ font-weight: bold; font-size: 1.2em; text-align: right; margin-top: 20px; }}
            </style>
        </head>
        <body>
            <h1>Invoice #{data.InvoiceNumber}</h1>
            <table>
                <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
                {string.Join("", data.Items.Select(i =>
                    $"<tr><td>{i.Name}</td><td>{i.Quantity}</td><td>${i.Price:F2}</td></tr>"))}
            </table>
            <p class='total'>Total: ${data.Total:F2}</p>
        </body>
        </html>";

        var pdf = renderer.RenderHtmlAsPdf(html);
        return pdf.BinaryData;
    }
}
Imports IronPdf

Public Class InvoiceGenerator
    Public Function GenerateInvoice(data As InvoiceData) As Byte()
        Dim renderer = New ChromePdfRenderer()

        Dim html As String = $"
        <!DOCTYPE html>
        <html>
        <head>
            <style>
                body {{ font-family: 'Segoe UI', sans-serif; margin: 40px; }}
                h1 {{ color: #2c3e50; }}
                table {{ width: 100%; border-collapse: collapse; }}
                th {{ background: #3498db; color: white; padding: 12px; text-align: left; }}
                td {{ border-bottom: 1px solid #e0e0e0; padding: 10px; }}
                .total {{ font-weight: bold; font-size: 1.2em; text-align: right; margin-top: 20px; }}
            </style>
        </head>
        <body>
            <h1>Invoice #{data.InvoiceNumber}</h1>
            <table>
                <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
                {String.Join("", data.Items.Select(Function(i) $"<tr><td>{i.Name}</td><td>{i.Quantity}</td><td>${i.Price:F2}</td></tr>"))}
            </table>
            <p class='total'>Total: ${data.Total:F2}</p>
        </body>
        </html>"

        Dim pdf = renderer.RenderHtmlAsPdf(html)
        Return pdf.BinaryData
    End Function
End Class
$vbLabelText   $csharpLabel

QuestPDF— Podejście z użyciem Fluent API

using QuestPDF.Fluent;
using QuestPDF.Infrastructure;

public class InvoiceGenerator
{
    public byte[] GenerateInvoice(InvoiceData data)
    {
        var document = Document.Create(container =>
        {
            container.Page(page =>
            {
                page.Size(PageSizes.A4);
                page.Margin(40);
                page.DefaultTextStyle(x => x.FontFamily("Segoe UI"));

                page.Header()
                    .Text($"Invoice #{data.InvoiceNumber}")
                    .FontSize(24).FontColor(Colors.Blue.Darken2);

                page.Content().Column(column =>
                {
                    column.Item().Table(table =>
                    {
                        table.ColumnsDefinition(cols =>
                        {
                            cols.RelativeColumn(3);
                            cols.RelativeColumn(1);
                            cols.RelativeColumn(1);
                        });

                        table.Header(header =>
                        {
                            header.Cell().Background(Colors.Blue.Medium).Padding(8)
                                .Text("Item").FontColor(Colors.White);
                            header.Cell().Background(Colors.Blue.Medium).Padding(8)
                                .Text("Qty").FontColor(Colors.White);
                            header.Cell().Background(Colors.Blue.Medium).Padding(8)
                                .Text("Price").FontColor(Colors.White);
                        });

                        foreach (var item in data.Items)
                        {
                            table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
                                .Padding(8).Text(item.Name);
                            table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
                                .Padding(8).Text(item.Quantity.ToString());
                            table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
                                .Padding(8).Text($"${item.Price:F2}");
                        }
                    });

                    column.Item().AlignRight().PaddingTop(20)
                        .Text($"Total: ${data.Total:F2}").FontSize(16).Bold();
                });
            });
        });

        using var stream = new MemoryStream();
        document.GeneratePdf(stream);
        return stream.ToArray();
    }
}
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;

public class InvoiceGenerator
{
    public byte[] GenerateInvoice(InvoiceData data)
    {
        var document = Document.Create(container =>
        {
            container.Page(page =>
            {
                page.Size(PageSizes.A4);
                page.Margin(40);
                page.DefaultTextStyle(x => x.FontFamily("Segoe UI"));

                page.Header()
                    .Text($"Invoice #{data.InvoiceNumber}")
                    .FontSize(24).FontColor(Colors.Blue.Darken2);

                page.Content().Column(column =>
                {
                    column.Item().Table(table =>
                    {
                        table.ColumnsDefinition(cols =>
                        {
                            cols.RelativeColumn(3);
                            cols.RelativeColumn(1);
                            cols.RelativeColumn(1);
                        });

                        table.Header(header =>
                        {
                            header.Cell().Background(Colors.Blue.Medium).Padding(8)
                                .Text("Item").FontColor(Colors.White);
                            header.Cell().Background(Colors.Blue.Medium).Padding(8)
                                .Text("Qty").FontColor(Colors.White);
                            header.Cell().Background(Colors.Blue.Medium).Padding(8)
                                .Text("Price").FontColor(Colors.White);
                        });

                        foreach (var item in data.Items)
                        {
                            table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
                                .Padding(8).Text(item.Name);
                            table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
                                .Padding(8).Text(item.Quantity.ToString());
                            table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
                                .Padding(8).Text($"${item.Price:F2}");
                        }
                    });

                    column.Item().AlignRight().PaddingTop(20)
                        .Text($"Total: ${data.Total:F2}").FontSize(16).Bold();
                });
            });
        });

        using var stream = new MemoryStream();
        document.GeneratePdf(stream);
        return stream.ToArray();
    }
}
Imports QuestPDF.Fluent
Imports QuestPDF.Infrastructure
Imports System.IO

Public Class InvoiceGenerator
    Public Function GenerateInvoice(data As InvoiceData) As Byte()
        Dim document = Document.Create(Sub(container)
                                           container.Page(Sub(page)
                                                              page.Size(PageSizes.A4)
                                                              page.Margin(40)
                                                              page.DefaultTextStyle(Function(x) x.FontFamily("Segoe UI"))

                                                              page.Header() _
                                                                  .Text($"Invoice #{data.InvoiceNumber}") _
                                                                  .FontSize(24).FontColor(Colors.Blue.Darken2)

                                                              page.Content().Column(Sub(column)
                                                                                       column.Item().Table(Sub(table)
                                                                                                               table.ColumnsDefinition(Sub(cols)
                                                                                                                                           cols.RelativeColumn(3)
                                                                                                                                           cols.RelativeColumn(1)
                                                                                                                                           cols.RelativeColumn(1)
                                                                                                                                       End Sub)

                                                                                                               table.Header(Sub(header)
                                                                                                                                header.Cell().Background(Colors.Blue.Medium).Padding(8) _
                                                                                                                                    .Text("Item").FontColor(Colors.White)
                                                                                                                                header.Cell().Background(Colors.Blue.Medium).Padding(8) _
                                                                                                                                    .Text("Qty").FontColor(Colors.White)
                                                                                                                                header.Cell().Background(Colors.Blue.Medium).Padding(8) _
                                                                                                                                    .Text("Price").FontColor(Colors.White)
                                                                                                                            End Sub)

                                                                                                               For Each item In data.Items
                                                                                                                   table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
                                                                                                                       .Padding(8).Text(item.Name)
                                                                                                                   table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
                                                                                                                       .Padding(8).Text(item.Quantity.ToString())
                                                                                                                   table.Cell().BorderBottom(1).BorderColor(Colors.Grey.Lighten2) _
                                                                                                                       .Padding(8).Text($"${item.Price:F2}")
                                                                                                               Next
                                                                                                           End Sub)

                                                                                       column.Item().AlignRight().PaddingTop(20) _
                                                                                           .Text($"Total: ${data.Total:F2}").FontSize(16).Bold()
                                                                                   End Sub)
                                                          End Sub)
                                       End Sub)

        Using stream As New MemoryStream()
            document.GeneratePdf(stream)
            Return stream.ToArray()
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

PdfSharp— Podejście z rysowaniem wspolrzednych

using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;

public class InvoiceGenerator
{
    public byte[] GenerateInvoice(InvoiceData data)
    {
        var document = new PdfDocument();
        var page = document.AddPage();
        var gfx = XGraphics.FromPdfPage(page);

        var titleFont = new XFont("Arial", 24);
        var headerFont = new XFont("Arial", 12, XFontStyleEx.Bold);
        var bodyFont = new XFont("Arial", 12);

        double y = 40;

        gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont,
            XBrushes.DarkBlue, 40, y);
        y += 50;

        // Table header — manually positioned
        double[] colX = { 40, 300, 400 };
        double rowHeight = 30;

        gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight);
        gfx.DrawString("Item", headerFont, XBrushes.White, colX[0] + 10, y + 20);
        gfx.DrawString("Qty", headerFont, XBrushes.White, colX[1] + 10, y + 20);
        gfx.DrawString("Price", headerFont, XBrushes.White, colX[2] + 10, y + 20);
        y += rowHeight;

        // Each row drawn individually with explicit coordinates
        foreach (var item in data.Items)
        {
            gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight);
            gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX[0] + 10, y + 20);
            gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX[1] + 10, y + 20);
            gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX[2] + 10, y + 20);
            y += rowHeight;
        }

        y += 20;
        gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y);

        using var stream = new MemoryStream();
        document.Save(stream);
        return stream.ToArray();
    }
}
using PdfSharpCore.Drawing;
using PdfSharpCore.Pdf;

public class InvoiceGenerator
{
    public byte[] GenerateInvoice(InvoiceData data)
    {
        var document = new PdfDocument();
        var page = document.AddPage();
        var gfx = XGraphics.FromPdfPage(page);

        var titleFont = new XFont("Arial", 24);
        var headerFont = new XFont("Arial", 12, XFontStyleEx.Bold);
        var bodyFont = new XFont("Arial", 12);

        double y = 40;

        gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont,
            XBrushes.DarkBlue, 40, y);
        y += 50;

        // Table header — manually positioned
        double[] colX = { 40, 300, 400 };
        double rowHeight = 30;

        gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight);
        gfx.DrawString("Item", headerFont, XBrushes.White, colX[0] + 10, y + 20);
        gfx.DrawString("Qty", headerFont, XBrushes.White, colX[1] + 10, y + 20);
        gfx.DrawString("Price", headerFont, XBrushes.White, colX[2] + 10, y + 20);
        y += rowHeight;

        // Each row drawn individually with explicit coordinates
        foreach (var item in data.Items)
        {
            gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight);
            gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX[0] + 10, y + 20);
            gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX[1] + 10, y + 20);
            gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX[2] + 10, y + 20);
            y += rowHeight;
        }

        y += 20;
        gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y);

        using var stream = new MemoryStream();
        document.Save(stream);
        return stream.ToArray();
    }
}
Imports PdfSharpCore.Drawing
Imports PdfSharpCore.Pdf
Imports System.IO

Public Class InvoiceGenerator
    Public Function GenerateInvoice(data As InvoiceData) As Byte()
        Dim document As New PdfDocument()
        Dim page = document.AddPage()
        Dim gfx = XGraphics.FromPdfPage(page)

        Dim titleFont As New XFont("Arial", 24)
        Dim headerFont As New XFont("Arial", 12, XFontStyleEx.Bold)
        Dim bodyFont As New XFont("Arial", 12)

        Dim y As Double = 40

        gfx.DrawString($"Invoice #{data.InvoiceNumber}", titleFont, XBrushes.DarkBlue, 40, y)
        y += 50

        ' Table header — manually positioned
        Dim colX As Double() = {40, 300, 400}
        Dim rowHeight As Double = 30

        gfx.DrawRectangle(XBrushes.SteelBlue, 40, y, 500, rowHeight)
        gfx.DrawString("Item", headerFont, XBrushes.White, colX(0) + 10, y + 20)
        gfx.DrawString("Qty", headerFont, XBrushes.White, colX(1) + 10, y + 20)
        gfx.DrawString("Price", headerFont, XBrushes.White, colX(2) + 10, y + 20)
        y += rowHeight

        ' Each row drawn individually with explicit coordinates
        For Each item In data.Items
            gfx.DrawRectangle(XPens.LightGray, 40, y, 500, rowHeight)
            gfx.DrawString(item.Name, bodyFont, XBrushes.Black, colX(0) + 10, y + 20)
            gfx.DrawString(item.Quantity.ToString(), bodyFont, XBrushes.Black, colX(1) + 10, y + 20)
            gfx.DrawString($"${item.Price:F2}", bodyFont, XBrushes.Black, colX(2) + 10, y + 20)
            y += rowHeight
        Next

        y += 20
        gfx.DrawString($"Total: ${data.Total:F2}", headerFont, XBrushes.Black, 440, y)

        Using stream As New MemoryStream()
            document.Save(stream)
            Return stream.ToArray()
        End Using
    End Function
End Class
$vbLabelText   $csharpLabel

WersjaIronPDFużywa HTML/CSS — umiejetnosci, które posiada większość programistów. WersjaQuestPDFwymaga nauki specyficznegodla domeny Fluent API, ale zapewnia strukture. Wersja PdfSharp wymaga recznego obliczania kazdej pozycji piksela — kazdego przesuniecia kolumny, kazdej wysokosci wiersza, kazdej granicy rysowanej indywidualnie.

Którą bibliotekę wybrać?

Kiedy oceniam te biblioteki, drzewo decyzji jest prostoliniowe:

Potrzebujesz HTML do PDF z nowoczesnym CSS? Praktyczne opcje toIronPDFlubPuppeteerSharp.IronPDFobsługuje Chromium wewnetrznie;Puppeteer Sharpwymaga zarządzania zewnętrznymi procesami przeglądarki.wkhtmltopdfnie jest opcja dla nowych projektów. pdfHTML iText nie może renderować Flexbox ani Grid.

Tworzenie dokumentów programistycznie z danych, bez HTML?QuestPDFoferuje produktywne i dobrze zaprojektowane Fluent API. PdfSharp zapewnia niższa kontrolę, ale wymaga znacznie wiekszego ilosci kodu dla odpowiednich układów.

Wdrożenie krzyzowkowe (Linux, Docker, chmura)? IronPDF,QuestPDFiPuppeteer Sharpdziałaja na Linuxie bez zależności libgdiplus. Aspose.PDF dokumentuje wycieki pamięci na Linuxie. PdfSharp ma częściowe wsparcie platformowe z znanymi problemami.

Ograniczenia licencyjne? PdfSharp (MIT) iPuppeteer Sharp(MIT) są darmowe bez warunków.QuestPDFjest darmowe do $1M przychodu. iText wymaga zgodności z AGPL lub licencji komercyjnej (od $15K–$210K/rok). Licencja wieczystaIronPDFzaczyna sie od $749.Asposeod ~$999.

Przed zaangażowaniem sie

Testuj przy użyciu rzeczywistej treści, a nie 'Hello World.' Wdrożenie do docelowej platformy wczesnie. Mierz pamięć na ponad 100 dokumentach, nie jednym. Przeczytaj całą treść licencji ze swoim zespolem prawnym. Sprawdz opóźnienie zimnego startu, jeżeli celem jest serverless.

IronPDF oferuje wersje próbna z pełna funkcjonalnościa do oceniania w zgodzie z twoimi specyficznymi wymaganiami.