How to Convert HTML with Images to PDF Using iTextSharp in C#
Convert controlled HTML and images to PDF in C# with iTextSharp/XML Worker or modern iText pdfHTML, including paths, base64 images, errors, and licensing.
Direct answer: For an existing iTextSharp 5 application, convert predictable XHTML with the separate XML Worker package. For new projects, use iText Core with the pdfHTML add-on. Set a base URI so relative image paths resolve, or embed images as data URLs. iTextSharp/XML Worker is a document converter, not a browser capable of rendering arbitrary websites.
Choose the right iText generation
Your package choice determines the API and the HTML you can support.
| Situation | Recommended path | Key limitation |
|---|---|---|
| Existing iTextSharp 5 codebase | iTextSharp 5 plus XML Worker from the same release line | Expects controlled XHTML/CSS; it is not a URL-to-PDF browser renderer. |
| New application | iText Core plus pdfHTML | Check the feature matrix for the exact package versions you deploy. |
| Small, simple HTML fragment | Legacy APIs may work | HTMLWorker was intended for snippets, is deprecated, and lacks full HTML/CSS support. |
iText identifies pdfHTML as the successor to XML Worker in the current iText Core family. The current reference reviewed for this guide lists pdfHTML 6.3.3 with iText Core 9.7.0; verify versions and supported tags against the release you install.
Prepare an iTextSharp 5 project
- Install
iTextSharpand the separateitextsharp.xmlworkerpackage. - Keep both packages on the same release line. Do not mix arbitrary DLL versions.
- Generate or obtain the final XHTML string before conversion. iTextSharp does not render ASP.NET, MVC, or Razor views itself.
- Make every stylesheet and image path available to the conversion process.
XML Worker maps predictable tags such as <p>, <img>, and <li> to PDF objects. It should not be treated as a general browser engine for JavaScript-heavy pages, modern layout systems, or arbitrary public URLs.
Complete iTextSharp/XML Worker example
The following console-style method converts an XHTML string and writes a PDF. It uses a file URI for local images and CSS.
using System;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.tool.xml;
public static class LegacyHtmlPdf
{
public static void Convert(string html, string outputPath, string resourceDirectory)
{
if (html == null) throw new ArgumentNullException(nameof(html));
if (outputPath == null) throw new ArgumentNullException(nameof(outputPath));
if (!Directory.Exists(resourceDirectory))
throw new DirectoryNotFoundException(resourceDirectory);
using (var document = new Document(PageSize.A4, 36, 36, 36, 36))
using (var stream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
{
PdfWriter writer = PdfWriter.GetInstance(document, stream);
document.Open();
using (var reader = new StringReader(html))
{
XMLWorkerHelper.GetInstance().ParseXHtml(
writer,
document,
reader,
null,
System.Text.Encoding.UTF8,
new UnicodeFontProvider());
}
}
}
}
// XML Worker needs a font provider when your HTML uses non-default glyphs.
public sealed class UnicodeFontProvider : iTextSharp.tool.xml.XMLWorkerFontProvider
{
public UnicodeFontProvider() : base( registerDirectories: true ) { }
}
// Example input. Use a file URI for reliable relative-resource resolution.
string baseUri = new Uri(Path.GetFullPath("wwwroot/"))
.AbsoluteUri;
string html = $"""
Report
Generated from controlled XHTML.
""";
LegacyHtmlPdf.Convert(html, "report.pdf", "wwwroot");
If your project targets a C# version without raw string literals, replace the sample HTML with a verbatim string (@"...") and escape embedded quotes.
Modern iText Core and pdfHTML
For new work, install the iText Core package and the pdfHTML add-on from NuGet with compatible versions. The central operation is HtmlConverter.ConvertToPdf. Set ConverterProperties.SetBaseUri to the directory containing resources referenced by relative URLs.
using System.IO;
using iText.Html2pdf;
using iText.Kernel.Pdf;
using iText.Layout;
public static class PdfHtmlExample
{
public static void CreatePdf(string baseUri, string html, string destination)
{
var properties = new ConverterProperties();
properties.SetBaseUri(baseUri);
using (var output = new FileStream(destination, FileMode.Create))
{
HtmlConverter.ConvertToPdf(html, output, properties);
}
}
}
string html = """
Invoice
This image is resolved relative to the base URI.
""";
string baseUri = new DirectoryInfo("/srv/app/wwwroot").FullName;
PdfHtmlExample.CreatePdf(baseUri, html, "/srv/app/out/invoice.pdf");
When converting directly from an HTML file, the source file’s parent directory can serve as the base URI. For an HTML string, set it explicitly. The process must have permission to read the image and stylesheet files.
Images: relative files, absolute URLs, and base64
Relative paths
Given <img src="images/logo.png">, a base URI of /srv/app/wwwroot resolves the image at /srv/app/wwwroot/images/logo.png. Confirm case-sensitive file names on Linux and avoid paths that only exist on a developer workstation.
Embedded data URLs
Embedding avoids a separate resource lookup. pdfHTML accepts a data URL:
byte[] bytes = File.ReadAllBytes("images/logo.png");
string base64 = Convert.ToBase64String(bytes);
string html = $"<html><body><img alt='Logo' src='data:image/png;base64,{base64}' /></body></html>";
HtmlConverter.ConvertToPdf(html, outputStream);
Use the correct MIME type, such as image/png or image/jpeg. Base64 increases HTML size, so use it selectively for small or self-contained documents.
Remote images
Do not assume XML Worker can fetch every HTTP URL or modern image format. For reliable conversion, download permitted assets first, store them in a controlled directory, and reference local files or data URLs. For pdfHTML, verify network access and supported resource behavior for your exact release.
HTML and CSS that convert predictably
- Emit well-formed XHTML: close elements, quote attributes, and include a UTF-8 declaration.
- Use simple CSS and test the exact tags and properties against the release feature matrix.
- Provide meaningful
alttext; it helps diagnostics even when the PDF is visual. - Replace browser-only JavaScript, canvas rendering, client-side data loading, and layout that depends on a live DOM.
- Keep print dimensions explicit when page breaks matter. Test long tables and images that span pages.
Why images disappear
- Log the final HTML and inspect each
srcvalue. - Resolve the URL manually against the configured base URI.
- Check file existence, process permissions, and filename case.
- For data URLs, validate the MIME type and base64 payload.
- Confirm the image format and CSS rule are supported by your package version.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
FileNotFoundException or blank image |
Relative path resolves from the wrong directory | Set SetBaseUri to the resource directory and verify the resolved path. |
| HTML appears as text or parsing fails | Malformed HTML or XML Worker-incompatible markup | Close tags, quote attributes, add an XHTML namespace where needed, and simplify the markup. |
| Fonts or symbols are missing | Font is unavailable to the converter | Install/register a font provider and use a font file available in the deployment environment. |
| CSS has no effect | Unsupported property, stylesheet path, or browser-only selector | Inline a small rule to isolate the issue, then consult the versioned feature matrix. |
| Conversion works locally but not in production | Different working directory, permissions, fonts, or network access | Use absolute deployment paths, package required assets, and log resource-resolution failures. |
| Pages are incomplete | HTML relies on JavaScript or asynchronous browser loading | Render data server-side before conversion or use a browser-based capture workflow. |
| DLL or type-load errors | Mixed iTextSharp/XML Worker versions | Align package versions and remove stale copied DLLs. |
Performance, reliability, and cost considerations
- Local files and embedded assets avoid unpredictable network latency.
- Reuse prepared templates and avoid unnecessarily large base64 payloads.
- Process large batches in bounded concurrency so memory and file handles stay predictable.
- Write to a temporary file or stream, then move the completed PDF into place after conversion succeeds.
- Record input identifiers, package versions, base URI, and conversion exceptions for repeatable debugging.
- There is no iTextSharp runtime service charge; account for your hosting resources and the applicable iText license.
Licensing and package compatibility
iText’s .NET guidance states that non-commercial use requires accepting the AGPL, while commercial deployments require commercial licenses for iText Core and pdfHTML. Confirm current terms for your organization and deployment. The pdfHTML dependency must match the Core version covered by your license. XML Worker and iTextSharp should likewise use matching release versions.
When a browser renderer is the better fit
If the requirement is a pixel-accurate screenshot or PDF of an arbitrary public website, a browser-based service handles consent banners, JavaScript, responsive layout, and network loading more naturally than XML Worker. For a source-controlled report with known XHTML and local assets, iText remains a practical document-generation choice.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. It can return PNG, JPEG, WebP, or PDF from one GET request. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.
See the ScreenshotNeo API documentation for all options, including full-page capture with lazy images, PDF paper size and margins, custom CSS and JavaScript, waits, request blocking, headers, cookies, geolocation, caching, signed links, asynchronous jobs, bulk capture, and usage reporting.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://stripe.com \
-o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://stripe.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);
ScreenshotNeo includes an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots each month without a card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.
FAQ
Can iTextSharp convert a complete website URL?
XML Worker was not designed as a URL-to-PDF browser renderer. Fetch and normalize controlled HTML yourself, or use a browser-based capture service for arbitrary sites.
Should new projects still use XML Worker?
Use it when maintaining an iTextSharp 5 application. For new applications, evaluate iText Core with pdfHTML and its current feature matrix.
Is base64 always better for images?
No. It makes a document self-contained but increases HTML size. Local files with a correct base URI are usually simpler for many assets.
Why does my PDF differ from Chrome?
iText conversion is not a full browser layout engine. Browser-only CSS, JavaScript, and asynchronous content can produce different output.


