Turn an HTML page into a tagged PDF in C# with OneRender, with no browser: set the page size and margins, write metadata, stream the file to a response, and keep links working.
using OneRender.Pdf; string html = """ <!DOCTYPE html> <html lang="en"> <head> <title>Packing slip 5521</title> <style> body { font: 11pt/1.4 sans-serif; margin: 0 } h1 { font-size: 16pt; margin: 0 0 8px } ul { padding-left: 18px } </style> </head> <body> <h1>Packing slip 5521</h1> <p>Ship to: Harbor Street Books, 14 Harbor Street</p> <ul><li>Shelf labels × 400</li><li>Reading lamps × 6</li></ul> <p><a href="https://example.com/track/5521">Track this order</a> · <a href="#returns">Returns</a></p> <h2 id="returns" style="break-before: page">Returns</h2> <p>Return anything within 30 days.</p> </body> </html> """; // 4 by 6 inches, a common label size, with quarter-inch margins. Units are CSS px: 96 per inch. var label = new PdfPageSetup(width: 4 * 96, height: 6 * 96, marginTop: 24, marginRight: 24, marginBottom: 24, marginLeft: 24); var options = new HtmlPdfOptions { Page = label, Output = new PdfOutputOptions { Author = "Harbor Street Books", Subject = "Order 5521", CreationDate = "D:20261027090000Z", // a PDF date string; the default is none }, }; byte[] bytes = HtmlPdf.ToPdf(html, options); File.WriteAllBytes("slip.pdf", bytes); Console.WriteLine($"slip.pdf: {bytes.Length:N0} bytes"); // In a web endpoint, write straight to the response body instead: pages stream out as they are finished. var response = new MemoryStream(); HtmlPdfResult result = HtmlPdf.ToPdf(html, response, options); Console.WriteLine($"{result.PageCount} pages, same bytes: {response.ToArray().AsSpan().SequenceEqual(bytes)}");
slip.pdf: 17,149 bytes 2 pages, same bytes: True
The file is 4 by 6 inches, two pages long, titled “Packing slip 5521” in English, with the author and subject set. The link to the tracking page is a link annotation, and “Returns” jumps to the heading on page 2.
What goes into the file
- Structure. The PDF is tagged from the HTML: headings, paragraphs, lists, tables with header scopes, links, and images with their
alttext. Turn it off withTagged = falseinPdfOutputOptions. - Title and language come from
<title>and<html lang>, unlessTitleorLanguageis set in the options. - Text is real text in subsetted fonts, so it can be selected, searched and extracted, including ligatures and right-to-left runs.
- Vector output. Backgrounds, borders, gradients, shadows without blur, transforms and clips are written as PDF drawing operations. Effects PDF can’t express, such as blurred shadows and filters, are rasterized at
RasterDpi(192 by default) for just that area. - The same bytes every time. Nothing reads the clock: with no
CreationDate, the same input gives the same file on every platform, which makes PDFs easy to compare in tests.
Good to know
- Pass a stream to write page by page. A
PdfProfileother thanNone(see Accessible PDF) builds the file in memory first, then writes it. PdfPageSetup.A4andPdfPageSetup.Letterhave 48 px (half-inch) margins. A@pagerule in the document wins over the options: see Headers, footers and page breaks.- Style sheets, images and fonts come through
BaseUrlandResources, as in Getting started. Scripts don’t run, so render the final HTML on the server, for example from a Razor component. MaxPages(100,000 by default) stops a runaway document with aPdfRenderException.
Every rule of the writer is in PDF output.