An alternate way of converting SVG to PNG with custom fonts in Azure Functions

post-thumb

Recently I was facing an integration challenge where I would have to convert an SVG file to PNG in order to store it in a Dynamics 365 image field. The difficulty hereby was that the SVG contained a custom font and the integration was powered by Azure Functions on Windows whose execution environment was missing the needed font and AFAIK there is no way of easily changing that. A common solution to this is usually to shovel the logic into a Docker container where a custom font can be installed and provided. But I was hoping for a solution which didn’t involve changing or managing Azure resources. So I got creative and came up with an idea on solving this from all within the existing Azure Function.

Basically, my idea was

  1. to embed the SVG as well as the needed web font within an HTML document.
  2. to upload the document to blob storage and generate a blob SAS URI.
  3. to navigate to the URL within a headless browser instance.
  4. to take a screenshot of the rendered SVG in memory.

For the headless browser part I used Microsoft Playwright for .NET . Actually its intended use is for E2E-testing, but I found it to be a perfect fit for my scenario, being well documented and able to easily install, run and orchestrate a headless chromium browser instance.

Talk is cheap. Show me the code. - Linus Torvalds

Here’s my working PoC1.

public async Task<byte[]> ConvertSvgToPngAsync(byte[] svg, BlobServiceClient blobServiceClient)
{
    // 1. Construct the html document with the svg and font embedded.

    string html = 
$@"
<!DOCTYPE html>
<html>
<head>
    <meta charset=""UTF-8"">
    <link href=""https://fonts.googleapis.com/css?family=Roboto"" rel=""stylesheet"">
</head>
<body>
    {Encoding.UTF8.GetString(svg)}
</body>
";

    // 2. Upload the document to blob storage and generate a sas uri.

    BlobClient blobClient = blobServiceClient
      .GetBlobContainerClient("tmp")
      .GetBlobClient("tmp.html");
    using MemoryStream stream = new(Encoding.UTF8.GetBytes(html));
    await blobClient.UploadAsync(stream, new BlobUploadOptions
    {
        HttpHeaders = new()
        {
            ContentType = "text/html"
        }
    });
    Uri uri = blobClient.GenerateSasUri(
      BlobSasPermissions.Read,
      DateTimeOffset.UtcNow.AddHours(1));
    
    // 3. Initialize a headless (chromium) browser instance and open the document online.

    Environment.SetEnvironmentVariable(
      "PLAYWRIGHT_BROWSERS_PATH",
      Environment.GetEnvironmentVariable("HOME_EXPANDED"));
    Microsoft.Playwright.Program.Main(new string[] { "install", "chromium", "--with-deps", });
    using var playwright = await Playwright.CreateAsync();
    await using var browser = await playwright.Chromium.LaunchAsync();
    var page = await browser.NewPageAsync();
    await page.SetViewportSizeAsync(1920, 1080);
    await page.GotoAsync(uri.ToString());

    // 4. Take an in-memory screenshot of the svg.

    await page.WaitForTimeoutAsync(5000);
    var element = await page.QuerySelectorAsync("svg");
    byte[] png = await element!.ScreenshotAsync(new()
    {
        OmitBackground = true,
    });

    return png;
}

Let me know what you think about this approach!


  1. You additionally have to adjust your .csproj-file in order to make Playwright work with Azure Functions as described here↩︎

Lernen Sie uns kennen

Das sind wir

Wir sind ein Software-Unternehmen mit Hauptsitz in Karlsruhe und auf die Umsetzung von Digitalstrategien durch vernetzte Cloud-Anwendungen spezialisiert. Wir sind Microsoft-Partner und erweitern Standard-Anwendungen bei Bedarf – egal ob Modernisierung, Integration, Implementierung von CRM- oder ERP-Systemen, Cloud Security oder Identity- und Access-Management: Wir unterstützen Sie!

Mehr über uns

Der Objektkultur-Newsletter

Mit unserem Newsletter informieren wir Sie stets über die neuesten Blogbeiträge,
Webcasts und weiteren spannenden Themen rund um die Digitalisierung.

Newsletter abonnieren