Generate PDF files using an html template and Playwright
There are many ways to generate PDF files. You can use a PDF library, but generating a PDF manually is not trivial. You can use a report library, but they are often paid solution and not trivial to use for simple needs.
In this post, we'll use an html template and Playwright to generate a PDF file. Using a browser to convert an html file to a PDF file is very convenient as it allows to use all html/CSS features. So, you can use CSS frameworks, custom fonts, etc. It also support multiple-page documents.
#Creating the html file
For the template, I use Scriban
as it is very simple scripting language to use and powerful enough for my use case. First you need to add a reference to the NuGet package:
<ItemGroup>
<PackageReference Include="Scriban" Version="4.0.1" />
</ItemGroup>
Then, you need to create an html file to store your template:
<!DOCTYPE html>
<html>
<head><link rel="stylesheet" href="invoice.css"></head>
<body style="padding: 3rem">
<h1>Invoice</h1>
Awesome company<br />
7026 Hunters Creek Dr<br />
<h2 style="margin-top: 3rem">Bill to</h2>
{{ invoice.customer.name | html.escape }}<br />
{{ invoice.customer.address | html.escape }}<br />
<div style="margin-top: 3rem">
Invoice No: #{{ invoice.id }}<br />
Date: #{{ invoice.created_at }}
</div>
<table class="table">
<thead>
<tr>
<th>Item Code</th>
<th>Description</th>
<th>Quantity</th>
<th>Unit Price</th>
<th>Total Price</th>
</tr>
</thead>
{{ for order_line in invoice.order_lines }}
<tr>
<td>{{ order_line.item_code | html.escape }}</td>
<td>{{ order_line.description | html.escape }}</td>
<td class="text-end">${{ order_line.quantity }}</td>
<td class="text-end">${{ order_line.unit_price | math.format "F2" }}</td>
<td class="text-end">${{ order_line.total_price | math.format "F2" }}</td>
</tr>
{{ end }}
<tfoot>
<tr>
<td class="text-end" colspan="4"><strong>Total:</strong></td>
<td class="text-end">${{ invoice.total_price | math.format "F2" }}</td>
</tr>
</tfoot>
</table>
</body>
</html>
Finally, you can load your template and render it:
// using Scriban;
// Load the template
// perf: you should parse the template once and reuse it for all rendering
var templateContent = File.ReadAllText("template.html");
var template = Template.Parse(templateContent);
// note: scriban convert names to snake case (OrderId => order_id)
// In your template, you must use {{ invoice.order_id }} instead of {{ Invoice.OrderId }}
var templateData = new { Invoice = LoadOrder(orderId) };
var pageContent = template.Render(templateData);
You now have an html file that you need to convert to PDF.
#Converting HTML to PDF
Playwright is a tool to automate a browser. You can navigate to pages, get element properties, execute javascript, take screenshots, record videos, etc. You can also use it to export a page to PDF or an image file.
First, you need to install the Playwright NuGet package and install download necessary browsers:
dotnet add package Microsoft.Playwright
dotnet build
playwright install
Then, you can open the page and export the page to PDF:
// using Microsoft.Playwright;
// In my case, I don't want to create a file, so I use a data url to open the html file
var dataUrl = "data:text/html;base64," + Convert.ToBase64String(Encoding.UTF8.GetBytes(pageContent));
// Open the page and wait for resources to be loaded (useful if you embed images or external stylesheets)
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = true,
});
await using var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync(dataUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
// Generate the PDF
var output = await page.PdfAsync(new PagePdfOptions
{
Format = "A4", // or "letter"
Landscape = false,
});
// Save the pdf to the disk
await File.WriteAllBytesAsync("output.pdf", output);
#Additional resources
- Convert SVG files to PNG or JPEG using .NET
- A framework for building Open Graph images
- scriban - GitHub
- Playwright
Do you have a question or a suggestion about this post? Contact me!