HTML a PDF en C# - La realidad de las opciones de biblioteca
Convertir HTML a PDF en C# requiere una biblioteca que realmente renderice HTML, no una que analice un subconjunto de etiquetas y se aproxime a CSS 2.1. La mayoría de las bibliotecas recomendadas en los hilos de Stack Overflow y en las discusiones de Reddit o bien no pueden renderizar CSS moderno, o tienen restricciones de licencia que las descalifican para uso comercial, o han sido abandonadas con vulnerabilidades de seguridad sin parchear.
Este artículo compara las bibliotecas que los desarrolladores encuentran realmente cuando buscan "HTML to PDF C#", documenta lo que cada una puede y no puede renderizar, incluye puntos de referencia de rendimiento con metodología y muestra el coste operativo real de cada enfoque.
Quickstart: HTML a PDF en C
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
pdf.SaveAs("output.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>");
pdf.SaveAs("output.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf("<h1>Hello World</h1>")
pdf.SaveAs("output.pdf")Instalar a través de NuGet: Install-Package IronPDF. Se implementa en Windows, Linux, macOS y Docker sin dependencias externas.
¿Por qué es difícil la conversión de HTML a PDF?
Renderizar HTML a PDF correctamente requiere la implementación de los mismos cinco componentes que utiliza un navegador web: un analizador HTML, un motor CSS (que incluye Flexbox, Grid, cascade, specificity y media queries), un tiempo de ejecución JavaScript, un motor de diseño y un canal de renderización que compone todo esto a PDF con precisión subpixel.
Las bibliotecas PDF tradicionales implementan las dos primeras parcialmente y omiten JavaScript por completo. Por eso se manejan con HTML sencillo pero se rompen en cualquier cosa que un navegador moderno renderice correctamente. La única forma de igualar la salida del navegador es utilizar un motor de navegador.
¿Qué bibliotecas convierten realmente HTML a PDF?
wkhtmltopdf Wrappers - El ecosistema de errores de carga de DLL
La consulta de búsqueda más común que lleva a los desarrolladores a estos artículos es alguna variación de:
System.DllNotFoundException: No se ha podido cargar la DLL 'libwkhtmltox'Las variantes específicas de plataforma incluyen:
No se ha podido cargar la biblioteca compartida 'wkhtmltox' o una de sus dependencias
(Linux — libwkhtmltox.so not found)
No se pudo encontrar el módulo especificado. (0x8007007E)
(Windows — wkhtmltox.dll path not configured)
dyld: Biblioteca no cargada: libwkhtmltox.dylib
(macOS — not supported on ARM64/Apple Silicon)Estos errores proceden de DinkToPdf, NReco.PdfGenerator, WkHtmlToXSharp y otras envolturas de C# en torno al mismo binario abandonado. La organización wkhtmltopdf GitHub se archivó en julio de 2024. El motor subyacente QtWebKit fue obsoleto por Qt en 2015. La página estado del proyecto lo marca explícitamente como obsoleto.
Más allá de los problemas de carga de DLL, el motor de renderizado está congelado en aproximadamente la capacidad de Safari 2011. Sin Flexbox, sin Grid, CSS3 limitado, JavaScript poco fiable. Y hay vulnerabilidades críticas sin parchear: CVE-2022-35583(CVSS 9.8) permite ataques SSRF que pueden filtrar credenciales de AWS a través de HTML manipulado.
el tiempo de wkhtmltopdf ha pasado. Los errores de carga de DLL son un síntoma de un problema más profundo: dependes de un software abandonado sin camino a seguir.
iText 7 (pdfHTML Add-On) - CSS Limitado, Licencia AGPL
el módulo pdfHTML de iText convierte HTML a PDF utilizando un analizador personalizado, no un motor de navegador. Maneja HTML/CSS básico pero no renderiza Flexbox, Grid o JavaScript.
El modo de fallo es silencioso: pdfHTML no lanza excepciones cuando encuentra CSS no soportado. Renderiza lo que puede e ignora el resto. Un contenedor display: flex con gap: 20px y justify-content: space-between se renderiza como elementos apilados verticalmente sin espaciado. Los desarrolladores descubren esto después de la integración, no durante.
Licencias: AGPL: requiere que toda la aplicación accesible a través de la red sea de código abierto o que se adquiera una licencia comercial. Los precios no se han publicado; los datos de terceros sugieren entre 15.000 y 210.000 dólares anuales.
¿Cómo se compara el uso de memoria?
pdfHTML de iText carga todo el documento en la memoria para su procesamiento. En el caso de los documentos empresariales típicos, esto es manejable, pero los informes HTML de gran tamaño con imágenes incrustadas pueden causar una presión de memoria significativa en comparación con los enfoques de flujo.
¿Por qué PdfSharp no es compatible con HTML?
PdfSharp aparece en los resultados de búsqueda de "HTML a PDF" debido a su popularidad (34,9 millones de descargas en NuGet) y a las frecuentes recomendaciones. Pero PdfSharp no tiene analizador HTML. Proporciona una API de dibujo basada en coordenadas: DrawString(), DrawRectangle(), DrawImage() con posiciones X/Y explícitas.
La solución alternativa que se sugiere habitualmente, HtmlRenderer.PdfSharp, solo es compatible con HTML 4.01 y CSS Nivel 2. Si tu HTML utiliza alguna característica CSS introducida después de 2010 —Flexbox (2012), Grid (2017), propiedades personalizadas (2017), border-radius (2011)—, no se mostrará.
Los desarrolladores que eligen PdfSharp esperando compatibilidad con HTML acaban colocando manualmente cada elemento con código basado en coordenadas o añadiendo una segunda biblioteca para la representación HTML, momento en el que PdfSharp resulta redundante.
¿Qué hace que Puppeteer Sharpconsuma muchos recursos?
Puppeteer Sharp controla Chrome a través de enlaces .NET. La precisión de la renderización coincide con Chrome porque es Chrome. El coste es operativo: se gestionan procesos de navegación externos.
Este es el aspecto real de la implementación de Puppeteer Sharpen producción, no el ejemplo de 5 líneas de los tutoriales, sino el código de agrupación de navegadores necesario para la generación simultánea de PDF:
using PuppeteerSharp;
using System.Collections.Concurrent;
public class PdfBrowserPool : IAsyncDisposable
{
private readonly ConcurrentBag<IBrowser> _available = new();
private readonly SemaphoreSlim _semaphore;
private readonly int _maxBrowsers;
public PdfBrowserPool(int maxBrowsers = 4)
{
_maxBrowsers = maxBrowsers;
_semaphore = new SemaphoreSlim(maxBrowsers, maxBrowsers);
}
public async Task InitializeAsync()
{
await new BrowserFetcher().DownloadAsync(); // ~280MB download
for (int i = 0; i < _maxBrowsers; i++)
{
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox",
"--disable-dev-shm-usage" }
});
_available.Add(browser);
}
}
public async Task<byte[]> ConvertHtmlToPdf(string html)
{
await _semaphore.WaitAsync();
IBrowser browser = null;
try
{
if (!_available.TryTake(out browser))
throw new InvalidOperationException("No browser available");
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
});
var result = await page.PdfAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true
});
return result;
}
catch (Exception ex) when (ex is NavigationException or TargetClosedException)
{
// Browser crashed — replace it
browser?.Dispose();
browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
});
throw; // Re-throw after recovery
}
finally
{
if (browser != null) _available.Add(browser);
_semaphore.Release();
}
}
public async ValueTask DisposeAsync()
{
foreach (var browser in _available)
{
await browser.CloseAsync();
browser.Dispose();
}
}
}using PuppeteerSharp;
using System.Collections.Concurrent;
public class PdfBrowserPool : IAsyncDisposable
{
private readonly ConcurrentBag<IBrowser> _available = new();
private readonly SemaphoreSlim _semaphore;
private readonly int _maxBrowsers;
public PdfBrowserPool(int maxBrowsers = 4)
{
_maxBrowsers = maxBrowsers;
_semaphore = new SemaphoreSlim(maxBrowsers, maxBrowsers);
}
public async Task InitializeAsync()
{
await new BrowserFetcher().DownloadAsync(); // ~280MB download
for (int i = 0; i < _maxBrowsers; i++)
{
var browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox",
"--disable-dev-shm-usage" }
});
_available.Add(browser);
}
}
public async Task<byte[]> ConvertHtmlToPdf(string html)
{
await _semaphore.WaitAsync();
IBrowser browser = null;
try
{
if (!_available.TryTake(out browser))
throw new InvalidOperationException("No browser available");
await using var page = await browser.NewPageAsync();
await page.SetContentAsync(html, new NavigationOptions
{
WaitUntil = new[] { WaitUntilNavigation.Networkidle0 }
});
var result = await page.PdfAsync(new PdfOptions
{
Format = PaperFormat.A4,
PrintBackground = true
});
return result;
}
catch (Exception ex) when (ex is NavigationException or TargetClosedException)
{
// Browser crashed — replace it
browser?.Dispose();
browser = await Puppeteer.LaunchAsync(new LaunchOptions
{
Headless = true,
Args = new[] { "--no-sandbox", "--disable-setuid-sandbox" }
});
throw; // Re-throw after recovery
}
finally
{
if (browser != null) _available.Add(browser);
_semaphore.Release();
}
}
public async ValueTask DisposeAsync()
{
foreach (var browser in _available)
{
await browser.CloseAsync();
browser.Dispose();
}
}
}Imports PuppeteerSharp
Imports System.Collections.Concurrent
Imports System.Threading
Public Class PdfBrowserPool
Implements IAsyncDisposable
Private ReadOnly _available As New ConcurrentBag(Of IBrowser)()
Private ReadOnly _semaphore As SemaphoreSlim
Private ReadOnly _maxBrowsers As Integer
Public Sub New(Optional maxBrowsers As Integer = 4)
_maxBrowsers = maxBrowsers
_semaphore = New SemaphoreSlim(maxBrowsers, maxBrowsers)
End Sub
Public Async Function InitializeAsync() As Task
Await (New BrowserFetcher()).DownloadAsync() ' ~280MB download
For i As Integer = 0 To _maxBrowsers - 1
Dim browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {
.Headless = True,
.Args = New String() {"--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage"}
})
_available.Add(browser)
Next
End Function
Public Async Function ConvertHtmlToPdf(html As String) As Task(Of Byte())
Await _semaphore.WaitAsync()
Dim browser As IBrowser = Nothing
Try
If Not _available.TryTake(browser) Then
Throw New InvalidOperationException("No browser available")
End If
Await Using page = Await browser.NewPageAsync()
Await page.SetContentAsync(html, New NavigationOptions With {
.WaitUntil = New WaitUntilNavigation() {WaitUntilNavigation.Networkidle0}
})
Dim result = Await page.PdfAsync(New PdfOptions With {
.Format = PaperFormat.A4,
.PrintBackground = True
})
Return result
End Using
Catch ex As Exception When TypeOf ex Is NavigationException OrElse TypeOf ex Is TargetClosedException
' Browser crashed — replace it
browser?.Dispose()
browser = Await Puppeteer.LaunchAsync(New LaunchOptions With {
.Headless = True,
.Args = New String() {"--no-sandbox", "--disable-setuid-sandbox"}
})
Throw ' Re-throw after recovery
Finally
If browser IsNot Nothing Then _available.Add(browser)
_semaphore.Release()
End Try
End Function
Public Async Function DisposeAsync() As ValueTask Implements IAsyncDisposable.DisposeAsync
For Each browser In _available
Await browser.CloseAsync()
browser.Dispose()
Next
End Function
End ClassSe trata de ~60 líneas de código de infraestructura antes de generar un solo PDF. También necesita monitorización de fugas de memoria (los procesos de Chromium acumulan memoria con el tiempo), comprobaciones de salud y un Dockerfile con más de 20 dependencias de Chromium. El tamaño de la imagen Docker aumenta entre 300 y 400 MB.
Compárelo con el enfoque equivalente de IronPDF:
using IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
// Browser pooling, process management, crash recovery — handled internallyusing IronPdf;
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
// Browser pooling, process management, crash recovery — handled internallyImports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim pdf = renderer.RenderHtmlAsPdf(html)
' Browser pooling, process management, crash recovery — handled internallyPuppeteer Sharp es viable si su equipo puede absorber los gastos operativos. Para los equipos que desean centrarse en su aplicación en lugar de en la infraestructura del navegador, IronPDF gestiona la misma renderización internamente.
¿Por qué QuestPDF no puede convertir HTML?
QuestPDF aparece prácticamente en todos los debates sobre "HTML a PDF C#" en Reddit y Stack Overflow. Esto crea un patrón consistente: los desarrolladores compran o integran QuestPDF esperando la conversión HTML, y luego descubren que no renderiza HTML en absoluto.
QuestPDF es una API de C# fluida para la creación programática de documentos. Su posicionamiento es explícitamente "deja de pelearte con la conversión de HTML a PDF": sustituye el enfoque HTML por código C#. Se trata de una elección de diseño deliberada. Las discusiones de GitHub desde 2022 hasta 2024 muestran a los desarrolladores descubriendo esto después de comenzar la implementación. Los responsables confirman que no está prevista la compatibilidad con HTML.
Si su flujo de trabajo actual utiliza plantillas HTML - vistas Razor para facturas, HTML de cuadros de mando para informes, contenido web para archivo - QuestPDF requiere reescribir cada plantilla en código API fluido C#. Para los nuevos proyectos en los que se crean diseños de documentos desde cero con datos estructurados, la API de QuestPDF está bien diseñada y es productiva.
La licencia comunitaria cubre empresas con ingresos brutos anuales inferiores a 1 millón de dólares. Además, se requiere una licencia comercial.
¿Qué hay de Aspose.PDF?
Aspose.PDF ofrece una amplia funcionalidad PDF con licencia comercial (a partir de ~999 $/desarrollador). La conversión HTML utiliza un motor personalizado, no un navegador - similar a iText, maneja HTML básico pero no renderiza las características CSS modernas con precisión.
La principal preocupación es la estabilidad de la plataforma: Asposedepende de System.Drawing.Common, que requiere libgdiplus en Linux. En .NET 6+, Microsoft dejó de utilizar esta función para plataformas distintas de Windows. Los desarrolladores informan de fugas de memoria específicas de las implantaciones de Linux que no se producen en Windows. Para entornos Windows solamente, Asposees capaz. En el caso de las implantaciones multiplataforma o en contenedores, la cadena de dependencia crea un riesgo continuo.
¿Cómo maneja IronPDF la conversión de HTML a PDF?
IronPDF integra Chromium directamente en el paquete NuGet. CSS Flexbox, Grid, propiedades personalizadas, @font-face, consultas de medios y JavaScript se ejecutan tal y como lo hacen en Chrome. El resultado debe coincidir con el navegador, ya que utiliza el mismo motor de renderizado.
using IronPdf;
var renderer = new ChromePdfRenderer();
string html = @"
<!DOCTYPE html>
<html>
<head>
<style>
:root { --primary: #2563eb; }
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
.card {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 12px; padding: 24px; text-align: center;
}
.card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
.card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
th { background: var(--primary); color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
<div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
<div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
</div>
<table>
<tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
<tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
<tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
</table>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
string html = @"
<!DOCTYPE html>
<html>
<head>
<style>
:root { --primary: #2563eb; }
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
.card {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 12px; padding: 24px; text-align: center;
}
.card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
.card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
th { background: var(--primary); color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
<div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
<div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
</div>
<table>
<tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
<tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
<tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
</table>
</body>
</html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("report.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
Dim html As String = "
<!DOCTYPE html>
<html>
<head>
<style>
:root { --primary: #2563eb; }
body { font-family: 'Segoe UI', sans-serif; padding: 40px; }
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 20px; }
.card {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
border-radius: 12px; padding: 24px; text-align: center;
}
.card h3 { color: #6b7280; font-size: 0.8rem; text-transform: uppercase; margin: 0; }
.card .value { font-size: 2rem; font-weight: 700; color: var(--primary); }
table { width: 100%; border-collapse: collapse; margin-top: 30px; }
th { background: var(--primary); color: white; padding: 12px; text-align: left; }
td { padding: 10px; border-bottom: 1px solid #e5e7eb; }
</style>
</head>
<body>
<div class='grid'>
<div class='card'><h3>Revenue</h3><div class='value'>$1.2M</div></div>
<div class='card'><h3>Users</h3><div class='value'>45,230</div></div>
<div class='card'><h3>Uptime</h3><div class='value'>99.97%</div></div>
</div>
<table>
<tr><th>Product</th><th>Revenue</th><th>Growth</th></tr>
<tr><td>Enterprise</td><td>$680K</td><td>+12%</td></tr>
<tr><td>Professional</td><td>$356K</td><td>+8%</td></tr>
</table>
</body>
</html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("report.pdf")Esto utiliza CSS Grid con minmax, propiedades personalizadas, linear-gradient, border-radius y :root selectores. Todas estas funciones fallan en pdfHTML de iText, no funcionan en wkhtmltopdf y no existen en PdfSharp ni en QuestPDF.
¿Cómo migrar desde otras bibliotecas?
Para los equipos que migran desde iTextSharp o wkhtmltopdf, IronPDF acepta URL directamente, lo que resulta útil cuando el flujo de trabajo existente genera archivos HTML o sirve páginas:
using IronPdf;
var renderer = new ChromePdfRenderer();
// Convert from URL — useful when migrating from wkhtmltopdf URL-based workflows
var pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly");
pdf.SaveAs("report.pdf");
// Convert from local HTML file
var pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html");
pdfFromFile.SaveAs("invoice.pdf");using IronPdf;
var renderer = new ChromePdfRenderer();
// Convert from URL — useful when migrating from wkhtmltopdf URL-based workflows
var pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly");
pdf.SaveAs("report.pdf");
// Convert from local HTML file
var pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html");
pdfFromFile.SaveAs("invoice.pdf");Imports IronPdf
Dim renderer As New ChromePdfRenderer()
' Convert from URL — useful when migrating from wkhtmltopdf URL-based workflows
Dim pdf = renderer.RenderUrlAsPdf("https://localhost:5001/reports/quarterly")
pdf.SaveAs("report.pdf")
' Convert from local HTML file
Dim pdfFromFile = renderer.RenderHtmlFileAsPdf("templates/invoice.html")
pdfFromFile.SaveAs("invoice.pdf")Despliegue
IronPDF funciona en Windows (x64), Linux (x64, ARM64), macOS (x64, Apple Silicon) y contenedores Docker. La configuración de Docker es una imagen .NET estándar:
FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]Sin instalación de Chromium, sin dependencias de bibliotecas nativas, sin configuración de sandbox.
Licencias: Licencias perpetuas a partir de 749 dólares. Precios publicados en ironpdf.com. Sin AGPL, sin tarifas por documento, sin umbrales de ingresos.
Parámetros de rendimiento
Probado en una máquina virtual Azure Standard_D4s_v3 (4 vCPU, 16 GB RAM) con Ubuntu 22.04 y .NET 8. Documento de prueba: una plantilla de factura HTML de 200 elementos con diseño de cuadrícula CSS, imágenes incrustadas y un gráfico generado por JavaScript. La media de cada medición fue de 50 iteraciones tras un periodo de calentamiento de 5 iteraciones.
| Escenario | IronPDF | Puppeteer Sharp | iText pdfHTML | wkhtmltopdf |
|---|---|---|---|---|
| HTML sencillo (sin JS) | ~150ms | ~500ms | ~200ms | ~200ms |
| CSS complejo (Flexbox/Grid) | ~250ms | ~600ms | Resultado roto | Resultado roto |
| Contenido renderizado en JavaScript | ~350ms | ~800ms | Fallos (sin motor JS) | Fallos/Parcial |
| Memoria por operación | ~80MB | ~150MB | ~60 MB | ~50 MB |
| Arranque en frío (primera generación) | 2-5s | 3-8s | <1s | <1s |
iText y wkhtmltopdf muestran arranques en frío más rápidos porque no inicializan un motor de navegación. Pero esta comparación sólo es significativa para escenarios en los que todas las bibliotecas producen resultados correctos, y para contenidos CSS o JavaScript complejos, sólo IronPDF y Puppeteer Sharp producen resultados utilizables.
Nota: Estas representan observaciones típicas en el hardware especificado. Su rendimiento variará en función de la complejidad del HTML, la longitud del documento y los recursos del servidor. Pruebe con sus cargas de trabajo reales antes de tomar decisiones.
Comparación de características
| Característica | IronPDF | iText 7 | Puppeteer Sharp | wkhtmltopdf | PdfSharp | QuestPDF | Aspose |
|---|---|---|---|---|---|---|---|
| HTML a PDF | Sí (Chromium) | Limitado (CSS 2.1) | Sí (Chrome) | Deprecated | No | No | Limitado |
| CSS Flexbox/Grid | Sí | No | Sí | No | No | No | No |
| Ejecución de JavaScript | Sí | No | Sí | Limitado | No | No | No |
| Multiplataforma (sin libgdiplus) | Sí | Sí | Sí | N/A | Parcial | Sí | No |
| Precios publicados | $749+ | No ($15K-$210K/año) | Gratuito (MIT) | Gratis | Gratuito (MIT) | Gratuito <$1M | $999+ |
| Mantenimiento activo | Sí | Sí | Sí | Abandonado | Sí | Sí | Sí |
¿Qué biblioteca elegir?
Plantillas HTML con CSS moderno → IronPDF proporciona Chromium incrustado sin gestión de procesos externos. Si su equipo puede gestionar la infraestructura del navegador, Puppeteer Sharpes una alternativa viable.
Generación programática de documentos a partir de datos, sin HTML → QuestPDF ofrece una elegante API fluida. No lo elijas esperando una conversión a HTML.
Manipulación sencilla de PDF (fusión, división, marca de agua) → PdfSharp es gratuito y capaz de realizar tareas que no sean HTML.
Evitar para nuevos proyectos: wkhtmltopdf (abandonado, CVEs), iText sin licencia comercial (trampa AGPL), Aspose en Linux (fugas de memoria).
La pregunta clave es si su flujo de trabajo utiliza plantillas HTML. Si es así, solo las soluciones basadas en Chromium producen resultados correctos con CSS moderno. Si no es así, la elección dependerá de las preferencias de la API y de las limitaciones de la licencia.
