COMPARISON

Convert HTML to PDF in .NET

Converting HTML to PDF in .NET remains one of the most searched developer topics, with nearly one million views on Stack Overflow alone. The demand is clear, but the solutions are not — traditional PDF libraries parse HTML rather than render it, producing broken layouts, missing styles, and silent failures with modern CSS. This article explains why HTML-to-PDF conversion is fundamentally difficult, documents the specific failure modes developers encounter, and demonstrates a Chromium-based approach that renders HTML exactly as a browser would.

Why Traditional HTML-to-PDF Libraries Fail

When developers search for "convert HTML to PDF in .NET," they expect the output to match what they see in Chrome. This expectation is reasonable but conflicts with how most .NET PDF libraries work. Libraries like iTextSharp, iText 7, and PdfSharp are PDF manipulation tools, not web rendering engines. They parse HTML and approximate styling rather than rendering it.

The gap between expectation and reality becomes obvious when developers try to convert modern HTML5 elements, CSS3 layouts with Flexbox and Grid, responsive designs with media queries, JavaScript-generated content like charts or dynamic tables, web fonts, or complex table layouts with merged cells and dynamic widths.

The result is broken layouts, missing styles, or outright failures.

The Root Cause: Five Components That Must Work Together

Understanding why this is hard prevents wasting time on solutions that can't work. Accurate HTML-to-PDF conversion requires five components operating together:

  1. HTML Parser — Must handle HTML5 semantic elements, nested structures, and malformed markup gracefully
  2. CSS Engine — Must implement the full CSS cascade: specificity, inheritance, media queries, Flexbox, Grid, custom properties, and @font-face
  3. JavaScript Runtime — Must execute JavaScript for dynamic content — charts rendered by Chart.js, tables populated by API calls, conditional layouts
  4. Layout Engine — Must calculate element positions using the same box model as browsers: margin collapsing, float clearing, overflow handling, page break logic
  5. Rendering Pipeline — Must composite the layout to PDF with sub-pixel accuracy: anti-aliased text, vector graphics, embedded fonts, color management

Traditional PDF libraries implement components 1 and 2 partially (often at CSS 2.1 levels) and skip 3 entirely. This is why iText's pdfHTML handles simple HTML but breaks on anything a modern browser would render correctly.

A browser engine implements all five. That's why the solution is to use a browser engine.

What Errors Developers Actually Encounter

When using iTextSharp's deprecated HTMLWorker:

iTextSharp.text.html.simpleparser.HTMLWorker is obsolete:
'Please use XMLWorkerHelper (iText.tool.xml) instead'

When using iText 7's pdfHTML add-on with modern HTML:

com.itextpdf.html2pdf.exceptions.CssApplierInitializationException:
Cannot find CSS applier for tag 'article'
com.itextpdf.html2pdf.exceptions.TagWorkerInitializationException:
Tag worker for element 'section' was not found

When using wkhtmltopdf on Linux:

Exit with code 1 due to network error: ProtocolUnknownError
wkhtmltopdf: symbol lookup error: wkhtmltopdf: undefined symbol

These aren't edge cases. They're the common experience of developers using these tools with standard HTML.

Common Rendering Symptoms

Beyond outright errors, these symptoms appear consistently across traditional libraries: tables render without proper column alignment, Flexbox layouts collapse into single columns, Grid layouts display as stacked divs, CSS gradients appear as solid colors or disappear, custom fonts fall back to system defaults, JavaScript content renders as blank space, and images with relative paths fail to load.

How Widespread Is This Problem?

The Stack Overflow question "Convert HTML to PDF in .NET" has 959,000+ views. That number alone tells the story, but the breadth is clearer in context:

ResourceViews/EngagementFirst Posted
Stack Overflow: Convert HTML to PDF in .NET959,034 viewsFebruary 2009
Stack Overflow: How to convert HTML to PDF using iTextSharp309,021 viewsAugust 2014
Reddit r/dotnet: HTML to PDF free library .NET 6.080+ commentsJanuary 2023
Stack Overflow: Export HTML to PDF in ASP.NET Core185,000+ viewsSeptember 2016

The problem spans .NET Framework 4.5 through 4.8, .NET Core 2.1 through 3.1, and .NET 5 through 8. It persists across all framework generations because the fundamental issue — traditional libraries don't render HTML — hasn't changed.

How the Ecosystem Evolved

DateEventSource
2009iTextSharp switches to AGPL, splitting the communityiText official announcement
2011wkhtmltopdf QtWebKit engine frozen at this era's capabilitiesQt project deprecation
2014Stack Overflow iTextSharp question reaches 100K+ viewsStack Overflow analytics
2016Qt officially removes QtWebKit from Qt 5.6Qt release notes
2019Microsoft begins System.Drawing.Common deprecation on non-Windows.NET runtime announcements
2020wkhtmltopdf enters maintenance-only modewkhtmltopdf status page
2022PdfSharp 6.0 ships still without HTML supportPdfSharp GitHub releases
2024wkhtmltopdf GitHub organization archivedGitHub
2025Chromium-based rendering becomes the standard approachIndustry adoption patterns

The trajectory is clear: the HTML rendering problem isn't being solved by traditional PDF libraries. It's being solved by embedding browser engines.

What the Developer Community Says

Stack Overflow Consensus

Across the top-voted answers on the primary Stack Overflow thread (959K views), the recommendations have shifted over time. Early answers (2009–2014) suggest iTextSharp and wkhtmltopdf. Newer answers (2020+) consistently recommend Chromium-based solutions:

"After trying several libraries, the only one that rendered our complex HTML templates with CSS Grid correctly was a Chromium-based approach. The traditional libraries all broke on modern CSS."

"We switched from wkhtmltopdf to IronPDF after discovering the SSRF vulnerability. The rendering quality improvement was a bonus."

To be transparent about trade-offs: Chromium-based rendering adds deployment weight. IronPDF's embedded Chromium increases the package size by approximately 200MB. For most server deployments this is irrelevant, but for size-constrained environments like edge functions, it's a consideration. The trade-off is worth it — a larger package that renders correctly beats a smaller one that produces broken output.

Reddit r/dotnet Discussions

A January 2023 thread titled "HTML to PDF free library .NET 6.0" generated 80+ comments. The discussion revealed a consistent pattern: developers start with free options, encounter limitations, and eventually adopt commercial libraries after investing significant development time in workarounds.

How IronPDF Solves the Rendering Problem

When we designed IronPDF, we chose embedded Chromium not because it was trendy, but because it was the only architecture that delivers consistent, predictable results. CSS Flexbox works. CSS Grid works. JavaScript executes. Web fonts render. The output matches Chrome because it is Chrome's rendering engine.

using IronPdf;

var renderer = new ChromePdfRenderer();

// This HTML uses CSS Grid, custom properties, and web fonts
// — features that break on every traditional PDF library
string html = @"
<!DOCTYPE html>
<html>
<head>
    <style>
        :root { --primary: #2563eb; --gray: #6b7280; }
        body { font-family: 'Segoe UI', system-ui, sans-serif; margin: 0; padding: 40px; }
        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 24px;
            margin-bottom: 40px;
        }
        .metric {
            background: linear-gradient(135deg, #f8fafc, #e2e8f0);
            border-radius: 12px;
            padding: 24px;
            text-align: center;
        }
        .metric h3 { color: var(--gray); font-size: 0.85rem; margin: 0 0 8px; text-transform: uppercase; }
        .metric .value { font-size: 2.5rem; font-weight: 700; color: var(--primary); }
        table { width: 100%; border-collapse: collapse; }
        th { background: var(--primary); color: white; padding: 12px 16px; text-align: left; }
        td { padding: 10px 16px; border-bottom: 1px solid #e5e7eb; }
        tr:nth-child(even) { background: #f9fafb; }
    </style>
</head>
<body>
    <div class='dashboard'>
        <div class='metric'><h3>Monthly Revenue</h3><div class='value'>$1.2M</div></div>
        <div class='metric'><h3>Active Users</h3><div class='value'>45,230</div></div>
        <div class='metric'><h3>Conversion Rate</h3><div class='value'>3.8%</div></div>
        <div class='metric'><h3>Uptime</h3><div class='value'>99.97%</div></div>
    </div>
    <table>
        <tr><th>Product</th><th>Units</th><th>Revenue</th><th>Growth</th></tr>
        <tr><td>Enterprise</td><td>142</td><td>$680,000</td><td>+12%</td></tr>
        <tr><td>Professional</td><td>891</td><td>$356,400</td><td>+8%</td></tr>
        <tr><td>Starter</td><td>2,340</td><td>$163,800</td><td>+23%</td></tr>
    </table>
</body>
</html>";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("dashboard-report.pdf");
using IronPdf;

var renderer = new ChromePdfRenderer();

// This HTML uses CSS Grid, custom properties, and web fonts
// — features that break on every traditional PDF library
string html = @"
<!DOCTYPE html>
<html>
<head>
    <style>
        :root { --primary: #2563eb; --gray: #6b7280; }
        body { font-family: 'Segoe UI', system-ui, sans-serif; margin: 0; padding: 40px; }
        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 24px;
            margin-bottom: 40px;
        }
        .metric {
            background: linear-gradient(135deg, #f8fafc, #e2e8f0);
            border-radius: 12px;
            padding: 24px;
            text-align: center;
        }
        .metric h3 { color: var(--gray); font-size: 0.85rem; margin: 0 0 8px; text-transform: uppercase; }
        .metric .value { font-size: 2.5rem; font-weight: 700; color: var(--primary); }
        table { width: 100%; border-collapse: collapse; }
        th { background: var(--primary); color: white; padding: 12px 16px; text-align: left; }
        td { padding: 10px 16px; border-bottom: 1px solid #e5e7eb; }
        tr:nth-child(even) { background: #f9fafb; }
    </style>
</head>
<body>
    <div class='dashboard'>
        <div class='metric'><h3>Monthly Revenue</h3><div class='value'>$1.2M</div></div>
        <div class='metric'><h3>Active Users</h3><div class='value'>45,230</div></div>
        <div class='metric'><h3>Conversion Rate</h3><div class='value'>3.8%</div></div>
        <div class='metric'><h3>Uptime</h3><div class='value'>99.97%</div></div>
    </div>
    <table>
        <tr><th>Product</th><th>Units</th><th>Revenue</th><th>Growth</th></tr>
        <tr><td>Enterprise</td><td>142</td><td>$680,000</td><td>+12%</td></tr>
        <tr><td>Professional</td><td>891</td><td>$356,400</td><td>+8%</td></tr>
        <tr><td>Starter</td><td>2,340</td><td>$163,800</td><td>+23%</td></tr>
    </table>
</body>
</html>";

var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("dashboard-report.pdf");
Imports IronPdf

Dim renderer As New ChromePdfRenderer()

' This HTML uses CSS Grid, custom properties, and web fonts
' — features that break on every traditional PDF library
Dim html As String = "
<!DOCTYPE html>
<html>
<head>
    <style>
        :root { --primary: #2563eb; --gray: #6b7280; }
        body { font-family: 'Segoe UI', system-ui, sans-serif; margin: 0; padding: 40px; }
        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
            gap: 24px;
            margin-bottom: 40px;
        }
        .metric {
            background: linear-gradient(135deg, #f8fafc, #e2e8f0);
            border-radius: 12px;
            padding: 24px;
            text-align: center;
        }
        .metric h3 { color: var(--gray); font-size: 0.85rem; margin: 0 0 8px; text-transform: uppercase; }
        .metric .value { font-size: 2.5rem; font-weight: 700; color: var(--primary); }
        table { width: 100%; border-collapse: collapse; }
        th { background: var(--primary); color: white; padding: 12px 16px; text-align: left; }
        td { padding: 10px 16px; border-bottom: 1px solid #e5e7eb; }
        tr:nth-child(even) { background: #f9fafb; }
    </style>
</head>
<body>
    <div class='dashboard'>
        <div class='metric'><h3>Monthly Revenue</h3><div class='value'>$1.2M</div></div>
        <div class='metric'><h3>Active Users</h3><div class='value'>45,230</div></div>
        <div class='metric'><h3>Conversion Rate</h3><div class='value'>3.8%</div></div>
        <div class='metric'><h3>Uptime</h3><div class='value'>99.97%</div></div>
    </div>
    <table>
        <tr><th>Product</th><th>Units</th><th>Revenue</th><th>Growth</th></tr>
        <tr><td>Enterprise</td><td>142</td><td>$680,000</td><td>+12%</td></tr>
        <tr><td>Professional</td><td>891</td><td>$356,400</td><td>+8%</td></tr>
        <tr><td>Starter</td><td>2,340</td><td>$163,800</td><td>+23%</td></tr>
    </table>
</body>
</html>"

Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("dashboard-report.pdf")
$vbLabelText   $csharpLabel

This example uses CSS Grid with auto-fit and minmax, CSS custom properties, linear-gradient, border-radius, :nth-child selectors, and system font stack. Every one of these features fails in iText's pdfHTML, breaks in wkhtmltopdf, and doesn't exist in PdfSharp or QuestPDF.

Platform Support

IronPDF runs on Windows (x64), Linux (x64, ARM64), macOS (x64, Apple Silicon), and Docker containers without System.Drawing.Common or libgdiplus dependencies. The Docker deployment is a standard .NET base image:

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY . .
ENTRYPOINT ["dotnet", "MyApp.dll"]

No additional packages, no native library installation, no special configuration.

API Differences from Traditional Libraries

For developers migrating from iTextSharp, the conceptual model is different. iTextSharp requires programmatic document construction; IronPDF accepts HTML as input:

TaskiTextSharp ApproachIronPDF Approach
Create a tableBuild PdfPTable with PdfPCell objectsWrite <table> in HTML
Style textSet Font objects on Chunk/PhraseWrite CSS
Add imagesCreate Image from path, set positionUse <img> tag
Page layoutSet Document margins and PageSizeUse CSS @page rules
Dynamic contentNot supportedJavaScript executes normally

What to Consider Before Migrating

Deployment Size

IronPDF's embedded Chromium adds approximately 200MB to the deployment package. On server deployments, Azure App Service, and Docker containers, this has no practical impact — the deployment happens once and the binary is cached. For Azure Functions consumption plan or AWS Lambda, check the deployment size limits against your function's total package size. IronPDF provides size optimization guidance for constrained environments.

Cold-Start Latency

The first PDF generation in a process takes 2–5 seconds as Chromium initializes. Subsequent generations are fast (100–500ms for typical documents). For serverless environments with cold starts, consider pre-warming strategies or using provisioned capacity. For long-running web servers and services, cold start is a one-time cost.

Memory Baseline

IronPDF's Chromium instance consumes approximately 150–200MB of memory at baseline. This is the cost of having a real browser engine. For comparison, Puppeteer Sharp has similar memory characteristics (it also uses Chromium), but requires you to manage the browser process lifecycle. IronPDF handles process management internally.

Plan for this memory budget in containerized deployments. A Docker container running IronPDF should have at least 512MB available; 1GB is recommended for processing complex documents.

Licensing Cost

IronPDF's perpetual license starts at $749 (1 developer, 1 project). Professional and enterprise tiers cover larger teams. Pricing is published at ironpdf.com. There are no per-document fees, no usage-based pricing, and no mandatory annual subscriptions.

The Recommendation

If your application needs to convert HTML to PDF with modern CSS support, the traditional library approach doesn't work. iTextSharp's pdfHTML can't render Flexbox or Grid. wkhtmltopdf is abandoned with unpatched CVEs. PdfSharp and QuestPDF don't parse HTML at all. Puppeteer Sharp renders correctly but requires managing external browser processes.

IronPDF embeds Chromium directly in the NuGet package — same rendering quality as Chrome, no external process management, no browser installation, no deployment headaches. Three lines of code to your first 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