Say, have you ever needed to render an inline SVG that uses an external font to a canvas? Of course you have, and this article will tell you how to do it without the fonts breaking.
If you just want to solve the problem:
<style>
tag inside a <defs>
tagFor my 💯 generator I wanted the option to turn the generated SVG into a nice copyable and shareable PNG.
The "standard" way to do this in the browser is the following:
Image
objectcanvas.drawImage()
canvas.toDataURL('image/png')
<img>
element and append it to the page.A simple, not convoluted at all, 5 step process.
Here is what the image should have been:
and instead this was the result:
Hmm. This is anything but the norm.
The issue here is that external styles aren't applied to SVGs in an img tag. The font is loaded from Google fonts via a stylesheet. For that to work, if the SVG is in an img tag, the stylesheet itself needs to be embedded inside the SVG.
There's more than that however, an SVG in an img tag has to be standalone, it can't request external resources, including that font. The font itself also needs to be embedded inside the stylesheet. To do that we first need to turn the font file into a data URL.
I used this script in the Firefox console to do this:
resp = await fetch('https://fonts.gstatic.com/s/ranga/v8/C8cg4cYisGb28qY-AygW43w.woff2'); blob = await resp.blob(); f = new FileReader(); f.addEventListener('load', () => console.log(f.result)); f.readAsDataURL(blob);
Replace the URL in fetch()
with the URL to your font.
This is easy, replace the URL in the url()
call:
<style> @font-face { font-family: 'Ranga'; /* .. */ src: url(data:font/woff2;base64,....) format('woff2'); } </style>
This is easy too, add it to SVG inside a <defs>
tag:
<svg xmlns="http://www.w3.org/2000/svg"> <defs> <style> @font-face { /* ... */ } </style> </defs> rest of SVG here... </svg>
Now we're clear to do the rest of steps above:
// convert the SVG to a data URL const svgText = new XMLSerializer().serializeToString(svgElement); const dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgText)}`; // create an image for that data URL const img = new Image(); img.src = dataUrl; img.onload = () => { // create a canvas const canvas = document.createElement('canvas'); canvas.width = svg.getBoundingClientRect().width; canvas.height = svg.getBoundingClientRect().height; // draw the image on to the canvas const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); // do something with the canvas // e.g. turn it into a PNG and add it to the document: const pngUrl = canvas.toDataURL('image/png'); const imgElement = document.createElement('img'); imgElement.src = pngUrl; document.body.appendChild(imgElement); };
Enjoy. I hope that saved you a wasted evening's hacking.