将带有自定义字体的 SVG 元素下载到 PNG

Download SVG element with custom font to PNG

我正在尝试向我正在构建的应用程序添加一项功能,让用户可以将呈现的 SVG 元素下载为 PNG 文件。该应用程序是使用 React 编写的,我决定使用 save-svg-to-png 包来实现这一点。到目前为止一切正常,但我对使用粗体 Roboto 字体的元素有问题 - 下载图像后,字体会恢复为默认字体(Times New Roman 或类似字体)。现在,我知道在可以传递给 saveSvgToPng 函数的 options 对象中有一个 fonts 参数,其签名如下:

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.

但是没有使用示例来阐明这一点,我也无法在网上找到任何示例。我遇到的问题似乎与 url 属性 有关 - 我不确定要在其中包含什么以使字体正确呈现。我尝试使用字体的 base64 编码生成 URI,并从 Google 字体站点 (https://fonts.googleapis.com/css2?family=Roboto:wght@700&display=swap) 添加 URL,但 none 似乎有效。谁能帮我解决这个问题?

我不是很乐观。 Using Fonts in SVGcss - How do I use a custom font in an SVG image on my site? - Graphic Design Stack ExchangeSVG text and Small, Scalable, Accessible Typographic Designs | CSS-Tricks 或多或少得出结论,当使用 <img> 显示时,无法在 SVG 中显示自定义字体。我知道这没有回答您的问题,但以下示例显示了如何将内联 SVG 加载到图像对象中,在 <canvas> 中绘制并作为数据 URL 导出到 PNG 图像。这是将 SVG 转换为 PNG 的基本过程,您可以看到它破坏了自定义字体。

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>

更新

好的,现在更乐观了...我用字体作为数据 URL 测试了我的示例,它确实有效。在下面的示例中,我将字体作为数据嵌入 URL(不幸的是字体数据太多,所以你只是一个占位符)。

这是结论(也是Robert Longson的初始评论):字体应该作为数据嵌入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>

您需要在 url 参数中提供指向实际字体文件 (.woff) 的 URI,并在 text 参数中提供 @font-face 规则。

请注意,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 之前的文件您可能会重复使用)。

所以在你的情况下你必须通过(假设你只有拉丁语范围内的字形)

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>