COMPARACIóN

Las mejores bibliotecas PDF de C# for .NET en 2026: Guía para el Mercado Español

La selección de una biblioteca PDF de C# afecta a la exposición del proyecto a licencias, la flexibilidad de implementación y el coste de mantenimiento a largo plazo. La mayoría de las bibliotecas que parecen adecuadas durante la evaluación revelan limitaciones en la producción: requisitos AGPL que no esperaba, representación HTML que no coincide con su navegador o fugas de memoria que sólo aparecen en Linux.

Este artículo compara las principales opciones con ejemplos de código, documenta las compensaciones que importan en la práctica e incluye una comparación de código lado a lado generando la misma factura a través de tres bibliotecas diferentes para que pueda ver las diferencias de la API directamente.

Inicio rápido: HTML a PDF en tres líneas

Instalación a través de NuGet:

Install-Package IronPDF

Genere 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")
$vbLabelText   $csharpLabel

Funciona en Windows, Linux, macOS y Docker sin configuración adicional. El resultado coincide con Chrome porqueIronPDFincorpora el mismo motor de renderizado Chromium.

Criterios de evaluación

Antes de comparar bibliotecas, sepa qué evaluar. Estas son las preguntas que hacen aflorar los problemas de producción en una fase temprana:

CriterioQué hay que comprobarPor qué es importante
Traducción HTML/CSSAlimente sus plantillas actuales con Flexbox/GridLa mayoría de las bibliotecas afirman ser compatibles con HTML, pero en el mejor de los casos presentan CSS 2.1
Ejecución de JavaScriptPrueba con Chart.js o contenido de tabla dinámicaLas bibliotecas sin soporte JS producen secciones en blanco
Modelo de licenciaLea la licencia completa, no el resumenLa AGPL exige que toda la aplicación sea de código abierto
PlataformasDespliegue en su entorno Linux/Docker/ARM64 de destinoEl éxito de Windows no predice el comportamiento de Linux
Memoria bajo cargaGeneración de más de 100 documentos en bucleLas pruebas de un solo documento ocultan fugas que bloquean los servidores de producción
Precios publicadosCompruebe si el precio está en el sitio web"Ventas por contacto" suele significar entre 15.000 y 210.000 dólares al año

Comparación de bibliotecas

IronPDF- Chromium integrado, compatibilidad total con CSS/JS

IronPDF integra Chromium directamente en el paquete NuGet. El renderizado HTML coincide con Chrome porque es el motor de Chrome. CSS Flexbox, Grid, propiedades personalizadas y JavaScriptse ejecutan como se espera.

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

Desventajas a tener en cuenta: El Chromium incrustado añade ~200MB al paquete de despliegue. Para las implantaciones estándar de servidores y contenedores, se trata de una descarga única sin impacto en el tiempo de ejecución. Para entornos con restricciones de tamaño como el plan de consumo de Azure Functions, compruebe el límite de tamaño de implementación. La primera generación de PDF en un proceso en frío tarda entre 2 y 5 segundos para la inicialización de Chromium; las siguientes generaciones se ejecutan en 100-500 ms. La memoria de referencia es de unos 150-200 MB; planifique los recursos del contenedor en consecuencia.

Licencias: Licencias perpetuas desde $2,998 (1 desarrollador). Precios publicados en ironpdf.com. Sin tarifas por documento, sin AGPL, sin umbrales de ingresos.

iText(iText) — Licenciamiento AGPL, HTML limitado

iText es una biblioteca de manipulación de PDF con una larga historia. El complemento pdfHTML ofrece conversión de HTML a PDF, pero no utiliza un motor de navegador, sino que se aproxima a CSS 2.1 con un analizador personalizado.

Un equipo de producción de una empresa mediana de SaaS descubrió esto cuando migró sus plantillas de facturas de Razor views. Las plantillas utilizan CSS Flexbox para diseños de columnas adaptables. Tras integrar pdfHTML de iText, cada factura se presentó como una pila vertical de una sola columna. Las propiedades display: flex, gap, y justify-content fueron ignoradas silenciosamente. El equipo empleó tres semanas en el desarrollo antes de darse cuenta de que pdfHTML no podía procesar el CSS existente.

La realidad AGPL: iTextutiliza la licencia AGPL. Si su aplicación es accesible a través de la red -lo que incluye todas las aplicaciones web, API y productos SaaS-, deberá publicar todo el código fuente de su aplicación bajo la licencia AGPL. No sólo el módulo PDF. Todo. iTexty su empresa matriz, Apryse, velan activamente por que así sea.

Licencia comercial: iTextpasó a la licencia por suscripción en 2024. Los precios no están publicados - póngase en contacto con ventas para obtener un presupuesto. Los datos de terceros sugieren entre 15.000 y 210.000 dólares anuales en función del volumen de uso.

PDFSharp— Licencia MIT, Sin HTML

PdfSharp es genuinamente gratuito bajo la licencia MIT con 34.9 millones de descargas en NuGet. La contrapartida es la capacidad: proporciona una API de dibujo basada en coordenadas sin analizador HTML, sin motor CSS y sin sistema de plantillas.

Un equipo construyendo un panel de informes eligió PdfSharp porque era gratuito y bien conocido. Pasaron cuatro meses escribiendo código de maquetación basado en coordenadas: calculando las posiciones X/Y de cada elemento de texto, dibujando los bordes de las tablas píxel a píxel y gestionando manualmente los saltos de página. Cuando finalmente compararon su resultado con lo que la misma plantilla HTML producía en un navegador, se dieron cuenta de que habían construido una versión peor de lo que una biblioteca basada en Chromium hace automáticamente.

PdfSharp funciona bien para fusionar PDFs, agregar marcas de agua y construir documentos estructurados simples a partir de datos. Si no necesitas renderizar HTML, sigue siendo una opción legítima.

QuestPDF - API elegante, sin HTML, umbral de ingresos

QuestPDF ofrece una API de C# fluida para crear documentos mediante programación. El diseño de la API es realmente bueno: es una de las mejores API de bibliotecas .NET de cualquier categoría.

Hay dos limitaciones importantes: QuestPDF no renderiza HTML (por diseño - se trata de una elección arquitectónica deliberada, no de una característica que falte), y la Licencia Comunitaria cubre empresas con ingresos brutos anuales inferiores a 1 millón de dólares. Una vez que su empresa cruce ese umbral, será obligatoria una licencia comercial. Las empresas que se acerquen al umbral deben presupuestar esta transición antes de que sea urgente.

A pesar del claro posicionamiento de QuestPDF en contra del HTML, los desarrolladores lo descubren regularmente después de iniciar la implementación porque la biblioteca aparece en los resultados de búsqueda de "biblioteca PDF de C#" junto a bibliotecas con capacidad HTML.

wkhtmltopdf Wrappers - CVEs abandonados, sin parches

el tiempo de wkhtmltopdf ha pasado. La organización GitHub fue archivada en julio de 2024. El motor subyacente QtWebKit fue obsoleto por Qt en 2015. Los CVEs conocidos - incluyendo CVE-2022-35583(CVSS 9.8, SSRF que permite la exfiltración de credenciales de AWS) - nunca serán parcheados.

Envoltorios de C# como DinkToPdf, NReco.PdfGenerator y WkHtmlToXSharp envuelven el mismo binario abandonado. El motor de renderizado está congelado en aproximadamente la capacidad de Safari 2011: sin Flexbox, sin Grid, JavaScriptlimitado. No es una opción viable para proyectos nuevos.

PuppeteerSharp- Renderización completa, complejidad operativa

Puppeteer Sharp controla Headless Chrome a través de enlaces .NET. La calidad de renderizado se corresponde con Chrome porque es Chrome. La contrapartida es operativa: se gestionan los procesos externos del navegador, incluidas las descargas, la agrupación, la supervisión de la memoria y la recuperación de bloqueos.

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

En producción, también se necesita la agrupación de procesos del navegador, la supervisión de fugas de memoria (los procesos de Chromium pueden tener fugas), la recuperación de fallos y la limpieza de recursos. El despliegue en Docker requiere la instalación de las dependencias de Chromium, lo que supone un archivo Dockerfile considerable en comparación con una imagen .NET estándar. PuppeteerSharpes viable si su equipo puede absorber los gastos operativos.

Aspose.PDF - Amplias funciones, problemas de memoria en Linux

Aspose.PDF ofrece una amplia funcionalidad PDF con una buena documentación. El problema más importante es la estabilidad de Linux: Asposedepende de System.Drawing.Common, que requiere libgdiplus en Linux, una biblioteca sin mantenimiento con fugas de memoria documentadas. Los informes para desarrolladores abarcan años:

"Varias docenas de peticiones hacen que el servicio se quede sin memoria en el entorno Unix, pero esto no ocurre en el entorno basado en Windows"

Para las implantaciones solo en Windows, Asposesigue siendo capaz. Para despliegues multiplataforma o en contenedores, la dependencia System.Drawing.Common crea un riesgo continuo. El precio de la licencia comercial es de aproximadamente 999 dólares por desarrollador.

Comparación de características

CaracterísticaIronPDFiTextPDFSharpQuestPDFwkhtmltopdfPuppeteerAspose
HTML a PDFCompleto (Chromium)Limitado (CSS 2.1)NoNoDeprecatedCompleto (Chrome)Limitado
CSS Flexbox/GridNoNoNoNoNo
JavaScriptNoNoNoLimitadoNo
Linux (sin libgdiplus)Parcial*N/ANo
Despliegue de DockerImagen de .NET StandardEstándarParcial*EstándarComplejoComplejoRequiere libgdiplus
Mantenimiento activoAbandonado
Precios publicadosSí ($2,998+)No ($15K-$210K/año)Gratuito (MIT)Sí (gratuito <$1M)GratisGratuito (MIT)Sí ($999+)
Licencia perpetuaNo (suscripción)N/AN/AN/AN/A
Libre de AGPLNo (requiere comercial)

*PDFSharp ha documentado problemas específicos de plataforma con algunas configuraciones.

Comparación de prestaciones

Probado en una máquina virtual en la nube de nivel medio (4 vCPU, 8 GB RAM) con una plantilla de factura HTML de 200 elementos, con un promedio de 50 iteraciones después del calentamiento:

EscenarioIronPDFPuppeteerSharpiTextpdfHTMLwkhtmltopdf
Página HTML sencilla~150ms~500ms~200ms~200ms
Diseño CSS complejo (Flexbox/Grid)~250ms~600msFallos/Parcial~400ms (entrecortado)
Página con mucho JavaScript~350ms~800msFallosFallos/Parcial
Memoria por operación~80MB~150MB~60 MB~50 MB
Arranque en frío (primera generación)2-5s3-8s<1s<1s

iText y wkhtmltopdf muestran arranques en frío más rápidos porque no inicializan el motor del navegador, pero tampoco pueden renderizar el mismo contenido. La comparación del rendimiento sólo tiene sentido en situaciones en las que todas las bibliotecas producen resultados correctos.

Comparación de códigos: Misma factura, tres bibliotecas

Las diferencias entre estas bibliotecas son más claras cuando se construye el mismo documento. He aquí una factura generada de tres maneras.

IronPDF- Enfoque 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 - Enfoque de API fluido

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— Enfoque de Dibujo por Coordenadas

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

La versiónIronPDFutiliza HTML/CSS, habilidades que la mayoría de los desarrolladores ya poseen. La versión QuestPDF requiere el aprendizaje de una API fluida específica del dominio, pero proporciona estructura. La versión PdfSharp requiere calcular manualmente la posición de cada píxel: cada desplazamiento de columna, cada altura de fila, cada borde dibujado individualmente.

¿Qué biblioteca elegir?

Cuando evalúo estas bibliotecas, el árbol de decisión es sencillo:

¿Necesita convertir HTML a PDF con CSS moderno? Las opciones prácticas sonIronPDFo PuppeteerSharp.IronPDFmaneja Chromium internamente; PuppeteerSharprequiere que gestiones procesos externos del navegador. wkhtmltopdf no es una opción para proyectos nuevos. pdfHTML de iTextno puede renderizar Flexbox o Grid.

Crear documentos mediante programación a partir de datos, sin HTML? La fluida API de QuestPDF es productiva y está bien diseñada. PdfSharp proporciona control a nivel más bajo, pero requiere significativamente más código para diseños equivalentes.

Despliegue multiplataforma (Linux, Docker, nube) IronPDF, QuestPDF y PuppeteerSharpfuncionan en Linux sin dependencias de libgdiplus. Aspose.PDF ha documentado fugas de memoria en Linux. PdfSharp tiene soporte parcial de plataformas con problemas conocidos.

¿Restricciones de licencia? PdfSharp (MIT) y PuppeteerSharp(MIT) son gratuitos sin condiciones. QuestPDF es gratuito por debajo de un millón de dólares de ingresos. iTextrequiere el cumplimiento de AGPL o licencia comercial ($15K-$210K/año). La licencia perpetua deIronPDFcomienza en $2,998. Asposecomienza en ~$999.

Antes de comprometerse

Pruebe con su contenido real, no con "Hello World" Despliegue pronto en la plataforma de destino. Mida la memoria en más de 100 documentos, no en uno. Lea el texto completo de la licencia con su equipo jurídico. Comprueba la latencia de arranque en frío si te diriges a serverless.

IronPDF ofrece una versión de prueba con todas las funciones para que pueda evaluar sus requisitos específicos.

Cumplimiento Normativo en España: Criterios de Selección de Biblioteca PDF

Para los equipos de desarrollo que trabajan en el mercado español, la elección de la biblioteca PDF va más allá de la fidelidad de renderizado HTML y el modelo de licencias. El marco regulatorio español impone requisitos técnicos concretos que la biblioteca seleccionada debe ser capaz de satisfacer.

Facturación Electrónica: VeriFactu y SII

La normativa española de facturación electrónica exige la generación de documentos que cumplan con el sistema *VERIFACTU (Verificable Fácilmente por la AEAT) y el SII (Suministro Inmediato de Información). Desde enero de 2026, las empresas sujetas al Impuesto sobre Sociedades con facturación superior a 6 millones de euros deben reportar facturas a la AEAT** en tiempo real.

IronPDF permite incrustar los metadatos y códigos QR exigidos por VERI*FACTU directamente en los documentos PDF generados desde plantillas HTML, facilitando la trazabilidad documental que requiere la AEAT. Las demás bibliotecas analizadas en este artículo —incluyendo iText, PdfSharp, QuestPDF, wkhtmltopdf, PuppeteerSharp y Aspose— no ofrecen soporte nativo para los estándares de registro SII ni para el formato Facturae (el formato XML de factura electrónica reconocido por FACe, el punto general de entrada de facturas electrónicas de la Administración).

Conformidad con la Ley Orgánica de Protección de Datos (LOPDGDD)

La LOPDGDD (Ley Orgánica 3/2018, de Protección de Datos Personales y Garantía de los Derechos Digitales) complementa el RGPD en España y establece obligaciones específicas para el tratamiento de datos personales incluidos en documentos PDF. La AEPD (Agencia Española de Protección de Datos) publica guías técnicas que recomiendan la redacción de datos personales antes de compartir documentos generados automáticamente.

IronPDF soporta redacción programática de regiones del PDF mediante su API de anotaciones y edición, permitiendo a los equipos implementar flujos de trabajo LOPDGDD-conformes con código .NET estándar. Las bibliotecas basadas en wkhtmltopdf como DinkToPdf, cuyo motor subyacente está abandonado y tiene CVEs sin parchear, representan un riesgo adicional en contextos sometidos a auditorías de la AEPD.

Firmas Digitales: eIDAS, PAdES y Certificados FNMT

El reglamento europeo eIDAS (Reglamento UE 910/2014) establece el marco legal para las firmas electrónicas reconocidas en España. Los documentos que requieren firma cualificada deben usar el estándar PAdES (PDF Advanced Electronic Signatures), compatible con los certificados emitidos por la FNMT (Fábrica Nacional de Moneda y Timbre) y otros prestadores cualificados de servicios de confianza.

IronPDF implementa firma digital con PAdES y soporta la incorporación de certificados FNMT en los flujos de firma. Esto es relevante para documentos contractuales, resoluciones administrativas y facturas electrónicas dirigidas a organismos de la Administración Pública española. En contraste, PDFSharp (MIT) requiere implementaciones personalizadas para PAdES, y QuestPDF no soporta firma digital en ninguno de sus niveles de licencia.

Facturación en Territorios Forales: TicketBAI

Las haciendas forales del País Vasco —Bizkaia, Gipuzkoa y Araba— han implantado el sistema TicketBAI (también denominado Batuz), que exige que todos los programas de facturación generen un fichero TicketBAI firmado electrónicamente con cada factura emitida, incluyendo un código QR vinculante. Navarra dispone del sistema NaTicket con requisitos similares.

IronPDF, combinado con las librerías de firma digital .NET adecuadas, puede generar los PDFs con los códigos QR de TicketBAI embebidos y los campos de metadatos requeridos por las haciendas forales. Las soluciones basadas en servicios cloud como api2pdf presentan incompatibilidades con estos requisitos, ya que el procesamiento en servidores de terceros puede vulnerar el artículo 5(1)(f) del RGPD (integridad y confidencialidad) tal como es interpretado por la AEPD.

Sector Público: Esquema Nacional de Seguridad (ENS)

El ENS (Esquema Nacional de Seguridad, Real Decreto 311/2022) establece los requisitos de seguridad para los sistemas de información de las Administraciones Públicas españolas. Las aplicaciones que generan documentación oficial deben cumplir con las categorías ENS correspondientes. La clasificación ENS requiere, entre otros aspectos, que las dependencias de software estén mantenidas activamente y que cuenten con actualizaciones de seguridad.

Este requisito descarta directamente bibliotecas como FO.NET (abandonada), DinkToPdf (última actualización 2018, wkhtmltopdf abandonado 2020) y PDFSharp en ciertas configuraciones con dependencias no mantenidas. IronPDF, con su ciclo de actualizaciones mensuales y parches de seguridad documentados, satisface los criterios de mantenimiento activo que exige el ENS para la categoría media.

Tabla Comparativa: Soporte Regulatorio España

Criterio de Cumplimiento EspañaIronPDFiTextQuestPDFwkhtmltopdf/DinkToPdfPuppeteerSharp
VERI*FACTU / SII metadataSí (vía HTML+metadatos)ParcialNoNoNo
LOPDGDD redacción de datosSí (API nativa)NoNoNo
PAdES / FNMT firmas eIDASSí (comercial)NoNoNo
TicketBAI QR embeddingParcialNoNoNo
ENS mantenimiento activoNo (abandonado)
Procesamiento local (LOPDGDD)

Sectores Regulados Adicionales

Las empresas del sector financiero supervisadas por el Banco de España o la CNMV (Comisión Nacional del Mercado de Valores) deben garantizar la trazabilidad e integridad de los documentos generados. IronPDF permite aplicar cifrado AES-256, permisos de documento y firmas de certificación que satisfacen los requisitos de integridad documental de estas entidades.

Para el sector de bebidas espirituosas y tabaco, el sistema SILICIE de la AEAT exige documentación de movimientos con metadatos específicos. Para empresas distribuidoras, el sistema SEVeM (Sistema de Emisión de Vales de Mezcla) requiere documentación trazable. IronPDF puede generar estos documentos con los campos de metadatos XMP requeridos.

La ley Crea y Crece (Ley 18/2022) impulsa la adopción de la factura electrónica en España a partir de 2025-2026, haciendo que la capacidad de generar Facturae XML y PDFs conformes sea un requisito de negocio para la mayoría de las empresas españolas. Al evaluar su biblioteca PDF para proyectos nuevos en 2026, incluya el cumplimiento de VERI*FACTU, LOPDGDD, eIDAS y ENS como criterios de selección junto a los criterios técnicos tradicionales.

Por favor notaApryse, Aspose, DinkToPdf, NReco, PDFSharp, PuppeteerSharp, QuestPDF, iText, y wkhtmltopdf son marcas registradas de sus respectivos propietarios. Este sitio no está afiliado, respaldado ni patrocinado por Apryse, AsposePty Ltd, CodeFlint, DinkToPdf, NReco, PDFTron, PuppeteerSharp, empira Software GmbH, iTextGroup, o wkhtmltopdf. Todos los nombres de producto, logotipos y marcas son propiedad de sus respectivos dueños. Las comparaciones son solo para fines informativos y reflejan información públicamente disponible en el momento de la redacción.