Home > Mobile >  Download SVG element with custom font to PNG
Download SVG element with custom font to PNG

Time:09-18

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?

CodePudding user response:

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;
}`    
    }
  ]
});

CodePudding user response:

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>

  • Related