Choisir la meilleure bibliothèque C# pour la génération de documents en 2026
Le choix d'une bibliothèque PDF C# a une incidence sur l'exposition de votre projet aux licences, la flexibilité du déploiement et le coût de la maintenance à long terme. La plupart des bibliothèques qui semblent convenir lors de l'évaluation révèlent des limites en production - des exigences AGPL auxquelles vous ne vous attendiez pas, un rendu HTML qui ne correspond pas à votre navigateur ou des fuites de mémoire qui n'apparaissent que sous Linux.
Cet article compare les principales options à l'aide d'exemples de code, documente les compromis qui comptent dans la pratique et inclut une comparaison de code côte à côte générant la même facture dans trois bibliothèques différentes afin que vous puissiez voir directement les différences entre les API.
Démarrage rapide : HTML à PDF en trois lignes
Installation via NuGet :
Installer le paquet IronPDF
Générer un 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")
Cette traduction fonctionne sur Windows, Linux, macOS et Docker sans configuration supplémentaire. Le résultat correspond à Chrome carIronPDFintègre le même moteur de rendu Chromium.
Critères d'évaluation
Avant de comparer les bibliothèques, sachez ce qu'il faut évaluer. Ce sont ces questions qui permettent de détecter rapidement les problèmes de production :
| Critère | Ce qu'il faut tester | Pourquoi c'est important |
|---|---|---|
| Rendu HTML/CSS | Nourrissez-le de vos modèles actuels avec Flexbox/Grid | La plupart des bibliothèques revendiquent la prise en charge du HTML, mais rendent au mieux le CSS 2.1 |
| Exécution JavaScript | Test avec Chart.js ou contenu de tableau dynamique | Les bibliothèques qui ne prennent pas en charge JS produisent des sections vides |
| Modèle de licence | Lisez la licence complète, pas le résumé | L'AGPL exige l'ouverture de l'ensemble de votre application |
| Prise en charge de la plate-forme | Déployez dans votre environnement Linux/Docker/ARM64 cible | Le succès de Windows ne prédit pas le comportement de Linux |
| Mémoire en charge | Générer plus de 100 documents en boucle | Les tests d'un seul document cachent les fuites qui font planter les serveurs de production |
| Prix publiés | Vérifier si les prix figurent sur le site web | "Contact sales" signifie souvent $15K-$210K/an |
Comparaison des bibliothèques
IronPDF- Chromium intégré, support CSS/JS complet
IronPDF intègre Chromium directement dans le paquet NuGet. Le rendu HTML correspond à Chrome car il est le moteur de Chrome. Les CSS Flexbox, Grid, les propriétés personnalisées et le JavaScripts'exécutent tous comme prévu.
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")
Trade-offs to consider: Le Chromium intégré ajoute ~200MB au paquet de déploiement. Pour les déploiements de serveurs et de conteneurs standard, il s'agit d'un téléchargement unique sans impact sur l'exécution. Pour les environnements soumis à des contraintes de taille comme le plan de consommation Azure Functions, vérifiez la limite de taille du déploiement. La première génération de PDF dans un processus à froid prend 2 à 5 secondes pour l'initialisation de Chromium ; les générations suivantes s'exécutent en 100-500 ms. La mémoire de base est de ~150-200MB - planifiez les ressources du conteneur en conséquence.
Licences:Licences perpétuelles à partir de 749 $ (1 développeur). Prix publiés sur ironpdf.com. Pas de frais par document, pas d'AGPL, pas de seuil de revenus.
iText 7 (iTextSharp) - Licence AGPL, HTML limité
iText est une bibliothèque de manipulation de PDF qui a une longue histoire. Le module complémentaire pdfHTML permet la conversion de HTML en PDF, mais il n'utilise pas de moteur de navigateur - il se rapproche de CSS 2.1 avec un analyseur personnalisé.
Une équipe de production d'une entreprise SaaS de taille moyenne a découvert cela lorsqu'elle a migré ses modèles de factures à partir des vues Razor. Les modèles utilisent CSS Flexbox pour des mises en page en colonnes réactives. Après l'intégration de pdfHTML d'iText, chaque facture s'est présentée sous la forme d'une pile verticale d'une seule colonne. Les propriétés display : flex, gap et justify-content ont été silencieusement ignorées. Trois semaines de développement ont été nécessaires avant que l'équipe ne se rende compte que pdfHTML ne pouvait pas rendre les feuilles de style CSS existantes.
La réalité AGPL: iText utilise la licence AGPL. Si votre application est accessible par le réseau - ce qui inclut toutes les applications web, les API et les produits SaaS - vous devez publier l'intégralité du code source de votre application sous AGPL. Pas seulement le module PDF. Tout. iText et sa société mère Apryse veillent activement au respect de ces règles.
Licences commerciales: iText est passé à une licence basée sur l'abonnement en 2024. La tarification n'est pas publiée - vous devez contacter le service commercial pour obtenir un devis. Les données de tiers suggèrent un montant annuel de 15 000 à 210 000 dollars, en fonction du volume d'utilisation.
PdfSharp - Licence MIT, Pas de HTML
PdfSharp est véritablement libre sous la licence MIT avec 34,9 millions de téléchargements NuGet. Le compromis est la capacité : il fournit une API de dessin basée sur les coordonnées sans analyseur HTML, sans moteur CSS et sans système de modèles.
Une équipe chargée d'élaborer un tableau de bord de reporting a choisi PdfSharp en raison de sa gratuité et de sa notoriété. Ils ont passé quatre mois à écrire un code de mise en page basé sur les coordonnées - calcul des positions X/Y pour chaque élément de texte, dessin des bordures de tableau pixel par pixel, gestion manuelle des sauts de page. Lorsqu'ils ont finalement comparé leur résultat à ce que le même modèle HTML produisait dans un navigateur, ils ont réalisé qu'ils avaient construit une version plus mauvaise de ce qu'une bibliothèque basée sur Chromium fait automatiquement.
PdfSharp permet de fusionner des PDF, d'ajouter des filigranes et de créer des documents structurés simples à partir de données. Si vous n'avez pas besoin du rendu HTML, il reste une option légitime.
QuestPDF - API élégante, pas de HTML, seuil de revenus
QuestPDF offre une API C# fluide permettant de créer des documents de manière programmatique. La conception de l'API est vraiment bonne - c'est l'une des meilleures API de bibliothèque .NET, toutes catégories confondues.
Deux contraintes sont à prendre en compte : QuestPDF ne rend pas le HTML (par conception - il s'agit d'un choix architectural délibéré et non d'une fonctionnalité manquante), et la licence communautaire couvre les entreprises dont le revenu brut annuel est inférieur à 1 million de dollars. Une fois que votre entreprise a franchi ce seuil, une licence commerciale devient obligatoire. Les entreprises qui s'approchent du seuil devraient prévoir un budget pour cette transition avant qu'elle ne devienne urgente.
Malgré le positionnement clair de QuestPDF contre le HTML, les développeurs le découvrent régulièrement après avoir commencé la mise en œuvre, car la bibliothèque apparaît dans les résultats de recherche "bibliothèque PDF C#" aux côtés des bibliothèques compatibles HTML.
wkhtmltopdfWrappers - Abandoned, Unpatched CVEs (en anglais)
le temps dewkhtmltopdfest révolu. L'organisation GitHub a été archivée en juillet 2024. Le moteur QtWebKit sous-jacent a été supprimé par Qt en 2015. Les CVE connues - y compris CVE-2022-35583 (CVSS 9.8, SSRF permettant l'exfiltration des identifiants AWS) - ne seront jamais corrigées.
Les wrappers C# tels que DinkToPdf, NReco.PdfGenerator et WkHtmlToXSharp enveloppent tous le même binaire abandonné. Le moteur de rendu est figé à la capacité approximative de Safari 2011 : pas de Flexbox, pas de Grid, JavaScriptlimité. Cette option n'est pas viable pour les nouveaux projets.
Marionnettiste pointu- Rendu complet, complexité opérationnelle
Puppeteer Sharp contrôle Chrome sans tête via des liaisons .NET. La qualité du rendu correspond à Chrome parce qu'il est Chrome. Le compromis est opérationnel : vous gérez les processus externes du navigateur, y compris les téléchargements, la mise en commun, la surveillance de la mémoire et la récupération en cas de panne.
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
En production, vous avez également besoin de la mise en commun des processus du navigateur, de la surveillance des fuites de mémoire (les processus de Chromium peuvent fuir), de la récupération en cas de panne et du nettoyage des ressources. Le déploiement Docker nécessite l'installation des dépendances de Chromium - un Dockerfile conséquent par rapport à une image .NET standard. Marionnettiste pointuest viable si votre équipe peut absorber les coûts opérationnels.
Aspose.PDF - Fonctionnalités étendues, problèmes de mémoire Linux
Aspose.PDF offre des fonctionnalités PDF étendues et une bonne documentation. Le problème majeur est la stabilité de Linux : Asposedépend de System.Drawing.Common, qui nécessite libgdiplus sous Linux - une bibliothèque non maintenue avec des fuites de mémoire documentées. Les rapports des développeurs s'étendent sur plusieurs années :
"Plusieurs dizaines de requêtes font que le service manque de mémoire dans l'environnement Unix, mais cela ne se produit pas dans l'environnement Windows"
Pour les déploiements sous Windows uniquement, Asposereste capable. Pour les déploiements multiplateformes ou conteneurisés, la dépendance System.Drawing.Common crée un risque permanent. Le prix de la licence commerciale est d'environ 999 dollars par développeur.
Comparaison des fonctionnalités
| Fonction | IronPDF | iText 7 | PdfSharp | QuestPDF | wkhtmltopdf | Marionnettiste | Aspose |
|---|---|---|---|---|---|---|---|
| HTML vers PDF | Complet (Chromium) | Limité (CSS 2.1) | Non | Non | Déclassé | Complet (Chrome) | Limité |
| CSS Flexbox/Grid | Oui | Non | Non | Non | Non | Oui | Non |
| JavaScript | Oui | Non | Non | Non | Limité | Oui | Non |
| Linux (pas de libgdiplus) | Oui | Oui | Partiel* | Oui | N/A | Oui | Non |
| Déploiement de Docker | Image .NET Standard | Standard de l'entreprise | Partiel* | Standard de l'entreprise | Complexe | Complexe | Nécessite libgdiplus |
| Maintenance active | Oui | Oui | Oui | Oui | Abandonné | Oui | Oui |
| Prix publiés | Oui ($749+) | Non ($15K-$210K/an) | Gratuit (MIT) | Oui (gratuit <1M$) | Gratuit | Gratuit (MIT) | Oui (999+) |
| Licence perpétuelle | Oui | Non (abonnement) | N/A | N/A | N/A | N/A | Oui |
| Sans AGPL | Oui | Non (nécessite un contrat commercial) | Oui | Oui | Oui | Oui | Oui |
*PdfSharp a documenté des problèmes spécifiques à la plateforme pour certaines configurations.
Comparaison des Performances
Testé sur une VM cloud de niveau intermédiaire (4 vCPU, 8 Go de RAM) avec un modèle de facture HTML de 200 éléments, avec une moyenne de 50 itérations après l'échauffement :
| Scénario | IronPDF | Marionnettiste pointu | iText pdfHTML | wkhtmltopdf |
|---|---|---|---|---|
| Page HTML simple | ~150ms | ~500ms | ~200ms | ~200ms |
| Mise en page CSS complexe (Flexbox/Grid) | ~250ms | ~600ms | Échecs/Partiel | ~400ms (cassé) |
| Page à forte composante JavaScript | ~350ms | ~800ms | Échecs | Échecs/Partiel |
| Mémoire par opération | ~80MB | ~150MB | ~60MB | ~50MB |
| Démarrage à froid (première génération) | 2-5s | 3-8s | <1s | <1s |
iText etwkhtmltopdfdémarrent plus rapidement parce qu'ils n'initialisent pas le moteur du navigateur, mais ils ne peuvent pas non plus restituer le même contenu. La comparaison des performances n'a de sens que dans les scénarios où toutes les bibliothèques produisent des résultats corrects.
Comparaison de code : Même facture, trois bibliothèques
Les différences entre ces bibliothèques sont plus évidentes lorsqu'il s'agit de construire le même document. Voici une facture générée de trois manières différentes.
IronPDF- Approche 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 - Approche fluide de l'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 - Approche du dessin de coordonnées
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
La versionIronPDFutilise HTML/CSS - des compétences que la plupart des développeurs possèdent déjà. La version QuestPDF nécessite l'apprentissage d'une API fluide spécifique au domaine mais fournit une structure. La version PdfSharp nécessite le calcul manuel de chaque position de pixel - chaque décalage de colonne, chaque hauteur de ligne, chaque bordure dessinée individuellement.
Quelle bibliothèque choisir ?
Lorsque j'évalue ces bibliothèques, l'arbre de décision est simple :
Vous avez besoin d'une conversion HTML-PDF avec des feuilles de style CSS modernes? Les options pratiques sontIronPDFou MarionnettisteSharp.IronPDFgère Chromium en interne ; Marionnettiste pointuvous oblige à gérer des processus externes dans le navigateur.wkhtmltopdfn'est pas une option pour les nouveaux projets. le pdfHTML d'iText ne peut pas rendre Flexbox ou Grid.
Construire des documents de manière programmatique à partir de données, sans HTML? L'API fluide de QuestPDF est productive et bien conçue. PdfSharp offre un contrôle de niveau inférieur mais nécessite beaucoup plus de code pour des mises en page équivalentes.
Déploiement multiplateforme (Linux, Docker, cloud)? IronPdf, QuestPDF et Marionnettiste pointufonctionnent sur Linux sans dépendances libgdiplus. Aspose.PDF a documenté les fuites de mémoire sous Linux. PdfSharp a une prise en charge partielle de la plateforme avec des problèmes connus.
Contraintes de licence? PdfSharp (MIT) et Marionnettiste pointu(MIT) sont libres sans conditions. QuestPDF est gratuit pour les entreprises dont le chiffre d'affaires est inférieur à 1 million de dollars. iText exige soit la conformité à l'AGPL, soit une licence commerciale (15 000 à 210 000 dollars par an). La licence perpétuelle d'IronPDF est proposée à partir de 749 $. Asposecommence à ~999 $.
Avant de vous engager
Testez avec votre contenu réel, pas avec "Hello World" Déployez votre produit sur la plateforme cible le plus tôt possible. Mesurez la mémoire sur plus de 100 documents, pas un seul. Lisez le texte intégral de la licence avec votre équipe juridique. Vérifiez la latence de démarrage à froid si vous ciblez le serverless.
IronPDF propose une version d'essai avec toutes les fonctionnalités pour une évaluation par rapport à vos besoins spécifiques.