SVG to PNG with JavaScript

I wanted to take an SVG file generated with some data from Rails and convert
that into a png that could be exported and used as a thumnail on YouTube. One
of the odd requirements for the SVG is that I want to use Google
Fonts
and many of the SVG to PNG options weren't
translating the font from a css @import statement..

I tried a few things that didn't work well and wanted to document incase others
encounter the same.

First, I thought I could just use imagemagic with the
rmagick gem and that would work however I
ran into several issues loading fonts.

Eventually, using JavaScript on the client with an HTML canvas element worked.
I found and modified this code snippet on
bl.ocks.org.

<div id="svg-container">
<%= File.read(File.join(Rails.root, "public", "thumb-template.svg")).html_safe %>
</div>

<canvas id="canvas" width="1920" height="1080"></canvas>
<div id="png-container"></div>

<script>
var svgString = new XMLSerializer().serializeToString(document.querySelector('svg'));
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([svgString], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
ctx.drawImage(img, 0, 0);
var png = canvas.toDataURL("image/png");
document.querySelector('#png-container').innerHTML = '<img src="'+png+'"/>';
DOMURL.revokeObjectURL(png);
};
img.src = url;
</script>

My workflow starts with designing the thumbnail in Figma, then exporting as SVG
replacing the SVG fonts that were exported by Figma and converting those into
<text> blocks with specific CSS classes to set the font family. Then,
rendering content into the text block with ERB.

<svg width="1920" height="1080" viewBox="0 0 1920 1080" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
  /* this bit doesn't work while converting to png: */
  @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;900');
  .subhead {
    fill: #C2F7EB;
    font-family: Roboto;
    font-weight: 300;
    font-size: 60px;
    text-transform: uppercase;
  }
  .title {
    fill: #F72585;
    font-family: Roboto;
    font-weight: 900;
    font-size: 144px;
  }
  </style>
  <g clip-path="url(#clip0)">
    <rect width="1920" height="1080" fill="white"/>
    <rect width="1920" height="1080" fill="#1B1725"/>
    <text x="77" y="250" class="subhead">Ruby Metaprogramming</text>
    <text x="77" y="420" class="title">Object#send</text>
  </g>
  <defs>
    <clipPath id="clip0">
      <rect width="1920" height="1080" fill="white"/>
    </clipPath>
  </defs>
</svg>

An SVG will load fine with the correct fonts when loaded in the browser using
an @import statement pointing at a font CDN like the one for Google fonts, however
when converting the SVG into a PNG those remote font faces are lost.

The next trick to get this bit working was to base64 encode the font file and
use a data url to embed the full content of the font into the CSS for styling
the text blocks.

If you download a font family from Google fonts, you'll get a set of .tff files.

Using the built in base64 tool on Mac, you can convert the file into base64 and
paste that into the font family like so:

<svg width="1920" height="1080" viewBox="0 0 1920 1080" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <style>
  @font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: url(data:font/truetype;charset=utf-8;base64,AAEAAAASAQAABAAgR0RFRnBqbY4AAaOkAAAB6kdQT1PZc2ujAAGlkAAATrpHU1VC0HjTzgAB9EwAAAoCT1MvMpcesZEA...)
  }
  @font-face {
    font-family: 'Roboto';
    font-style: normal;
    font-weight: 900;
    font-display: swap;
    src: url(data:font/truetype;charset=utf-8;base64,AAEAAAASAQAABAAgR0RFRnBqbY4AAaAMAAAB6kdQT1MfGyUBAAGh+AAAVhxHU1VC0HjTzgAB+BQAAAoCT1MvMpl2sdgA...)
  }
  .subhead {
    fill: #C2F7EB;
    font-family: Roboto;
    font-weight: 300;
    font-size: 60px;
    text-transform: uppercase;
  }
  .title {
    fill: #F72585;
    font-family: Roboto;
    font-weight: 900;
    font-size: 144px;
  }
  </style>
  <g clip-path="url(#clip0)">
    <rect width="1920" height="1080" fill="white"/>
    <rect width="1920" height="1080" fill="#1B1725"/>
    <text x="77" y="250" class="subhead">Ruby Metaprogramming</text>
    <text x="77" y="420" class="title">Object#send</text>
  </g>
  <defs>
    <clipPath id="clip0">
      <rect width="1920" height="1080" fill="white"/>
    </clipPath>
  </defs>
</svg>