Reputation: 1215
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
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
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>
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