Move HTML-to-PDF and screenshot jobs off Puppeteer, Playwright or wkhtmltopdf onto OneRender: no Chromium download, no browser process, and the same output on every machine. Here is each common call and what replaces it.
using System.Text; using OneRender; using OneRender.Image; using OneRender.Interaction; using OneRender.Pdf; // What a headless browser would load from the network, the host supplies. var files = new Dictionary<string, byte[]> { ["https://app.example/css/report.css"] = Encoding.UTF8.GetBytes(""" body { font: 15px/1.5 sans-serif; margin: 24px; color: #222 } .card { border: 1px solid #ddd; border-radius: 8px; padding: 12px 16px; margin: 0 0 12px } @media print { .no-print { display: none } } @page { size: A4; margin: 20mm; @bottom-center { content: "Page " counter(page) " of " counter(pages); font: 9pt sans-serif } } """), }; byte[]? Resources(string url) => files.GetValueOrDefault(url); var body = new StringBuilder(); for (int i = 1; i <= 12; i++) body.Append($"<div class=card><h2>Week {i}</h2><p>{i * 37} orders shipped.</p></div>"); string html = $""" <!DOCTYPE html> <html lang="en"><head><title>Shipping report</title><link rel="stylesheet" href="/css/report.css"></head> <body><button class="no-print">Download</button>{body}</body></html> """; const string baseUrl = "https://app.example/reports/weekly"; // page.pdf({ format: 'A4', printBackground: true, displayHeaderFooter: true, ... }) HtmlPdfResult pdf = HtmlPdf.ToPdf(html, File.Create("report.pdf"), new HtmlPdfOptions { BaseUrl = baseUrl, Resources = Resources }); Console.WriteLine($"report.pdf: {pdf.PageCount} pages"); // page.setViewport({ width: 800, height: 600, deviceScaleFactor: 2 }); page.screenshot() RgbaImage shot = HtmlRender.ToImage(html, new HtmlRenderOptions { Width = 800, Height = 600, DevicePixelRatio = 2, BaseUrl = baseUrl, Resources = Resources }); File.WriteAllBytes("viewport.png", shot.ToPng()); Console.WriteLine($"viewport.png: {shot.Width}x{shot.Height}"); // page.screenshot({ fullPage: true }): measure the document, then render that tall. var measure = InteractivePage.Create(html, new HtmlPageOptions(FontProviders.CreateDefault()) { ViewportWidth = 800, ViewportHeight = 600, BaseUrl = baseUrl, StylesheetBytes = Resources }); int height = measure.Layout.ScrollHeight(measure.Page.Document.DocumentElement); RgbaImage full = HtmlRender.ToImage(html, new HtmlRenderOptions { Width = 800, Height = height, BaseUrl = baseUrl, Resources = Resources }); File.WriteAllBytes("full.png", full.ToPng()); Console.WriteLine($"full.png: {full.Width}x{full.Height}");
report.pdf: 2 pages viewport.png: 1600x1200 full.png: 800x1818
The PDF uses print media, so the Download button is gone, and the page numbers come from the @page rule. The screenshots use screen media and keep the button.
Call by call
| Headless browser | OneRender |
|---|---|
page.setContent(html) | pass the HTML string |
page.goto(url) | fetch the HTML yourself, set BaseUrl to its address |
network requests, waitUntil: 'networkidle0' | the Resources callback returns each style sheet, image and font; nothing is fetched |
page.pdf({ format, margin }) | HtmlPdf.ToPdf with PdfPageSetup, or @page { size; margin } in CSS |
printBackground: true | always on: backgrounds are painted |
displayHeaderFooter, headerTemplate, footerTemplate | @page margin boxes with counter(page), counter(pages) and string() |
page.emulateMediaType('print') | HtmlPdf uses print media; HtmlPageOptions.PrintMedia for anything else |
page.screenshot() | HtmlRender.ToImage with Width, Height and DevicePixelRatio |
screenshot({ fullPage: true }) | measure with ScrollHeight, then render at that height, as above |
screenshot({ omitBackground: true }) | PageColor = PaintColor.Transparent |
element.boundingBox() | Layout.GetBoundingClientRect(node) on an InteractivePage |
page.evaluate(...) | not available: there is no JavaScript |
Full-page screenshots
ScrollHeight of the root element is the height of the whole document at that viewport width, as document.documentElement.scrollHeight is in a browser. HtmlRender.ToImage accepts up to 16,384 CSS pixels on each side, so a longer page belongs in a PDF.
What changes
- No JavaScript. Scripts are inert text, so a page that builds itself in the browser renders empty. Render the final HTML on the server, for example with Razor (Render a Razor component to PDF).
- No fonts from the machine. Text uses the bundled fonts and the packages you add, so output doesn’t change between a laptop and a container. See Fonts.
- No process to manage. Rendering is a function call in your process: no Chromium download, no sandbox flags, no browser pool or crashed tabs, and it works with NativeAOT.
- Tagged PDFs. Output is tagged from the HTML semantics by default, and PDF/UA-1 or PDF/A-2a is one option away (Accessible PDF).
- Gaps to check for.
<img>of an SVG file isn’t drawn (inline<svg>is), there are no vertical writing modes or CSS footnotes, and repeated table headers don’t show in 1.0.0 (Headers, footers and page breaks).
How close is it to Chrome?
Layout is compared with Chrome for Testing 156 on the same font files, element by element. On the six document templates, 99.967% of the geometry is within tolerance at both 1280 by 800 and 390 by 844. Web Platform Tests reftests gate each CSS module. The method and the numbers are in Geometry against Chrome and Web Platform Tests.