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")
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")
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
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
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
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
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.