Skip to content

HTML to PDF in C# without a browser

Since 1.1View as Markdown

PdfNetStack.Html turns HTML and CSS into PDF with a layout engine written in C#: no browser, no native binaries, no JavaScript.

Call HtmlConverter.ConvertToPdf with an HTML string to get the PDF bytes. The markup is parsed the way browsers repair it, styled with the CSS cascade, laid out in boxes and split into pages by the paged media rules.

byte[] pdf = HtmlConverter.ConvertToPdf("<h1>Invoice 42</h1><p>Total: <b>1,210.00 EUR</b></p>");
File.WriteAllBytes("invoice.pdf", pdf);

The package is PdfNetStack.Html. It depends on the PdfNetStack version it ships with, and runs on .NET 8 and later and on .NET Framework 4.6.2 and later through .NET Standard 2.0.

ConvertFileToDocument reads an HTML file and resolves relative URLs against its folder, so style sheets, images and fonts next to the template load. Files outside that folder do not.

// Relative URLs (fonts, logo) resolve against the file's folder, and only that folder is read.
PdfDocument doc = HtmlConverter.ConvertFileToDocument("invoice.html");
doc.Save("invoice.pdf");

An invoice converted from HTML: logo, addresses in a two column grid, an item table and totals

The template behind this invoice uses @font-face with WOFF2 files, custom properties, flexbox, grid, a table with striped rows and a gradient. Its <title>, <meta name="author"> and lang attribute become the PDF title, author and language.

MethodInputOutput
ConvertToPdf(html, options)An HTML stringThe PDF as a byte array
ConvertToPdf(html, stream, options)An HTML stringThe PDF written to a stream
ConvertToDocument(html, options)An HTML stringA PdfDocument to change before saving
ConvertFileToDocument(path, options)An HTML file, read as UTF-8 unless it starts with a byte order markA PdfDocument, with the file’s folder as the base

All of them take an optional HtmlConverterOptions.

var fonts = new PdfFontCollection();
fonts.AddDirectory("fonts"); // .ttf, .otf, .woff and .woff2 files
var options = new HtmlConverterOptions
{
BaseUri = new Uri(Path.GetFullPath(".") + Path.DirectorySeparatorChar), // relative URLs and file access
PageStyle = "size: A5; margin: 15mm", // applies before the document's @page rules
StyleSheet = "body { font-family: Inter, sans-serif; font-size: 10pt }", // a house style
Fonts = fonts, // looked up before installed fonts
UseSystemFonts = false, // same output on every machine
Bookmarks = true, // headings become bookmarks (default)
Tagged = true, // structure tree for screen readers (default)
};
OptionDefaultNotes
BaseUrinullWhere relative URLs point: a folder as a file URI, or a web address when ResourceLoader fetches from the network. ConvertFileToDocument sets it to the file’s location when you leave it null
ResourceLoadernullFetches style sheets, images and fonts by absolute URL; returns null for anything that must not load. Without it only data: URIs and files under BaseUri’s folder load
PageStylesize: A4; margin: 2cmPage size and margins as CSS, applied before the document’s own @page rules
StyleSheetnullA style sheet applied before the document’s own, for house styles
FontsnullA PdfFontCollection searched before installed fonts
UseSystemFontstrueWith false, output depends only on the HTML, the fonts you pass and the standard 14 fonts, and is the same on every machine
BookmarkstrueHeadings become bookmarks, following the bookmark-level property
TaggedtrueWrites a tagged PDF with a structure tree in reading order

Resource loading and fonts are covered in Fonts and assets.

ConvertToDocument returns a PdfDocument, so the rest of PdfNetStack applies before the file is written: attach files, save as PDF/A, sign, encrypt, merge or stamp pages.

PdfDocument doc = HtmlConverter.ConvertToDocument(html, options);
// The result is a PdfDocument like any other: attach, sign, encrypt, merge or stamp it.
doc.Info.Author = "Logistics";
doc.Attachments.Add("delivery.xml", Encoding.UTF8.GetBytes("<delivery id=\"881\"/>"), "text/xml");
doc.Save("delivery-note.pdf", new PdfSaveOptions { Conformance = PdfConformance.PdfA3U });

This sample writes a PDF/A-3u file with an XML attachment. PDF/A needs every font embedded, which is why the options above pass Inter in Fonts and turn installed fonts off. See PDF/A, Signatures and Edit pages.

To write straight to a stream, as an HTTP response or a blob upload would:

using (FileStream output = File.Create("delivery-note-plain.pdf"))
{
HtmlConverter.ConvertToPdf(html, output, options);
}

By default the output is a tagged PDF (ISO 32000-1, 14.8). Headings, paragraphs, lists, tables with header and data cells, figures with their alt text, links tied to their annotations, sections and quotes form a structure tree in reading order. Backgrounds, borders, text shadows, repeated table headers, fixed boxes and page margins are marked as artifacts, so screen readers skip them. The catalog is marked and viewers show the document title. Set Tagged = false to leave the structure out.

Links to web addresses and to anchors in the same document become PDF links. Headings h1 to h6 become nested bookmarks, from the bookmark-level property the default style sheet sets on them; set Bookmarks = false to write none.

  • HTML. A tolerant parser that repairs markup as browsers do, and the legacy presentational attributes old templates rely on (align, bgcolor, width, cellpadding, cellspacing, border, font).
  • CSS. The cascade with !important, inheritance, var(), Selectors Level 4, @media print, @import, @font-face, @page, calc(), min(), max(), clamp() and CSS Color 4.
  • Layout. Block, inline, table, flex and grid layout (with named lines, dense packing, subgrid, auto-fill and auto-fit), floats, relative, absolute and fixed positioning in z-index stacking order, inline blocks, lists, counters and generated content.
  • Paged media. Page sizes and margins, margin boxes, page counters, named pages, running headers and footers, forced and avoided breaks, repeated table headers. See Paged media.
  • Text. Unicode line breaking, right to left and Arabic text with contextual shaping, Indic scripts, kerning, ligatures and fallback fonts per character.

The full list, module by module, is on CSS support.

Without a ResourceLoader, nothing is fetched from the network and only files under the base folder are read, so converting HTML from users cannot reach internal services or other files. Parsing, styling and layout are bounded: nesting depth, selector complexity, var() expansion, @import chains, table spans, grid tracks and background tiling all have limits, so hostile markup ends in a finite document rather than a hang or a crash.

  • No JavaScript: pages must be rendered HTML, not built in the browser.
  • No multi-column layout or vertical writing modes yet.
  • Images: JPEG, PNG, GIF, BMP, TIFF and WebP (the first frame of animations), and SVG drawn as vectors without filters, masks or patterns.
  • The page property takes no effect inside flex, grid and table containers. An inline running element shows on every page rather than from where it stands.
  • Variable fonts draw their default instance: font-weight does not choose an instance yet.
  • Box shadow blur is approximated, text-shadow blur is not drawn, and group opacity is applied per element.

Questions

Does PdfNetStack.Html run a browser or JavaScript?
No. It is a layout engine written in C# that parses HTML, applies CSS and lays out pages itself. Scripts are not run, so the HTML must already contain the content to print.
Does HTML to PDF work on Linux and in containers?
Yes. The package is managed code with no native binaries, so it runs wherever .NET runs. Set UseSystemFonts to false and supply fonts for output that is the same on every machine.
Is the text in the PDF searchable?
Yes. Text is written as real text with the fonts embedded or referenced, so it selects, copies and searches, and the output is a tagged PDF by default.