Maciej B. Nowak
Maciej B. Nowak

Reputation: 1215

Download SVG element with custom font to PNG

I'm trying to add a functionality to the app I'm building that lets the user to download a rendered SVG element as a PNG file. The app is written using React and I decided to use save-svg-to-png package to achieve this. Everything works so far, but I have a problem with an element that uses a bold Roboto font - after downloading the image, the font falls back to the default (Times New Roman or something like that). Now, I know that among the options object that can be passed to saveSvgToPng function there is a fonts parameter with a signature like so:

fonts - A list of {text, url, format} objects the specify what fonts to inline in the SVG. Omitting this option defaults to auto-detecting font rules.

But there are no usage examples to clarify that, neither I could find any example on the web. The problem I have seems to be with the url property - I'm not sure what to include there to make the font render properly. I tried to generate a URI with base64 encoding of the font and adding the URL from the Google Fonts site (https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap), but none seem to work. Can anyone help me with figuring this out?

Upvotes: 1

Views: 1685

Answers (2)

Kaiido
Kaiido

Reputation: 137044

You need to provide both the URI pointing to the actual font file (.woff) in the url param, and the @font-face rule in the text param.

Note that the file your url points to is just a CSS file, the actual font files are still to be grasped from there (if you need a programmatic way, I wrote something some times ago you could probably reuse).

So in your case you'd have to pass (assuming you only have glyphs in the Latin range)

saveSvgAsPng(element, file_name, {
  fonts: [
    {
      url: "https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2",
      text: `
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}`    
    }
  ]
});

// using svgAsDataUri instead of saveSvgAsPng to avoid downloading the file
svgAsDataUri(document.getElementById("target"), {
    fonts: [
    {
    url: "https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2",
    text: `
@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/roboto/v27/KFOlCnqEu92Fr1MmWUlfBBc4AMP6lQ.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}`    
   }
  ]
}).then( uri => {
    const img = new Image();
  img.src = uri;
  document.body.append(img);
});
@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap");
<script src="https://cdn.jsdelivr.net/gh/exupero/saveSvgAsPng/src/saveSvgAsPng.js"></script>
<svg id="target">
  <text font-family="Roboto" y="50">My text</text>
</svg>

Upvotes: 2

chrwahl
chrwahl

Reputation: 13125

I'm not so optimistic. Using Fonts in SVG and css - How do I use a custom font in an SVG image on my site? - Graphic Design Stack Exchange and SVG text and Small, Scalable, Accessible Typographic Designs | CSS-Tricks more or less concludes that it is not possible to display custom fonts in SVGs when they are displayed using <img>. I know this is not answering your question but the following example shows how a inline SVG is loaded into an image object, drawn in <canvas> and exported as a data URL to a PNG image. This is the underlying process for transforming a SVG to PNG and you can see that it breaks the custom font.

document.addEventListener('DOMContentLoaded', e => {
  let img = document.images[0];
  let svg = document.getElementById('svg');
  let image = new Image();
  let canvas = document.getElementById('canvas');
  let ctx = canvas.getContext('2d');
  
  image.addEventListener('load', e => {
    ctx.drawImage(e.target, 0, 0, 300, 30);
    img.src = canvas.toDataURL("image/png");
  });
  image.src = "data:image/svg+xml," + svg.outerHTML;
});
<p>SVG image:</p>
<svg id="svg" viewBox="0 0 100 10" width="300" height="30" xmlns="http://www.w3.org/2000/svg">
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap');
    svg{
      font-family: Roboto, sans-serif;
    }
    text {
      font-family:'Roboto';
      font-weight: bold;
    }
  </style>
  <text font-size="10" text-anchor="middle" dominant-baseline="middle" x="50" y="5">Test</text>
</svg>
<p>Canvas image:</p>
<canvas id="canvas" width="300" height="30"></canvas>
<p>PNG image:</p>
<p><img /></p>

Update

OK, now more optimistic... I tested my example with the font as a data URL and it actually works. In the following example I have embedded the font as a data URL (unfortunately the font is too much data, so thee is just a placeholder).

This is the conclusion (and also Robert Longsons initial comment): The font should be embedded as a data URL.

document.addEventListener('DOMContentLoaded', e => {
  let img = document.images[0];
  let svg = document.getElementById('svg');
  let image = new Image();
  let canvas = document.getElementById('canvas');
  let ctx = canvas.getContext('2d');
  
  image.addEventListener('load', e => {
    ctx.drawImage(e.target, 0, 0, 300, 30);
    img.src = canvas.toDataURL("image/png");
  });
  image.src = "data:image/svg+xml," + svg.outerHTML;
});
<p>SVG image:</p>
<svg id="svg" viewBox="0 0 100 10" width="300" height="30" xmlns="http://www.w3.org/2000/svg">
  <style>
    <![CDATA[
    @font-face {
      font-family: Roboto;
      src: url('data:font/ttf;base64,[  ----  font data goes here  ------ ]') format("truetype");
    }
    svg{
      font-family: Roboto, sans-serif;
    }
    text {
      font-family: Roboto;
    }
    ]]>
  </style>
  <text font-size="10" text-anchor="middle" dominant-baseline="middle" x="50" y="5">Test</text>
</svg>
<p>Canvas image:</p>
<canvas id="canvas" width="300" height="30"></canvas>
<p>PNG image:</p>
<p><img /></p>

Upvotes: 1

Related Questions