在SVG中绘制DOM对象时如何在Canvas中使用Google字体?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在SVG中绘制DOM对象时如何在Canvas中使用Google字体?相关的知识,希望对你有一定的参考价值。

根据Mozilla的文档,您可以在Canvas上绘制复杂的html,如this

我无法弄清楚的是让谷歌字体与它一起工作的方法。

请参阅以下示例:

var canvas = document.getElementById('canvas');
    var ctx    = canvas.getContext('2d');
    
    var data   = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
                   '<foreignObject width="100%" height="100%">' +
                     '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px;font-family:Pangolin">' +
                       'test' +
                     '</div>' +
                   '</foreignObject>' +
                 '</svg>';
    
    var DOMURL = window.URL || window.webkitURL || window;
    
    var img = new Image();
    var svg = new Blob([data], {type: 'image/svg+xml;charset=utf-8'});
    var url = DOMURL.createObjectURL(svg);
    
    img.onload = function () {
      ctx.drawImage(img, 0, 0);
      DOMURL.revokeObjectURL(url);
    }
    
    img.src = url;
<link href="https://fonts.googleapis.com/css?family=Pangolin" rel="stylesheet">

<div style="font-size:40px;font-family:Pangolin">test</div><hr>
<canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas>
答案

这已经被问了几次,但从来没有像谷歌字体那么精确。

所以一般的想法是:

  • 要在画布上绘制svg,我们需要先在<img>元素中加载它。
  • 出于安全原因,<img>内部文档无法提出任何外部请求。 这意味着在将其加载到<img>元素之前,您必须将所有外部资源作为dataURI嵌入到svg标记本身中。

因此,对于字体,您需要附加一个<style>元素,并使用dataURI版本替换url(...)之间的字体src。

谷歌字体嵌入文件就像你使用的文件实际上只是css文件,然后指向实际的字体文件。因此,我们不仅需要获取第一级CSS文档,还需要获取实际的字体文件。

这是一个注释和工作(?)概念证明,用ES6语法编写,因此需要一个现代浏览器,但它可以很容易地编译,因为它中的所有方法都可以进行多边形。

/*
  Only tested on a really limited set of fonts, can very well not work
  This should be taken as an proof of concept rather than a solid script.
	
  @Params : an url pointing to an embed Google Font stylesheet
  @Returns : a Promise, fulfiled with all the cssRules converted to dataURI as an Array
*/
function GFontToDataURI(url) {
  return fetch(url) // first fecth the embed stylesheet page
    .then(resp => resp.text()) // we only need the text of it
    .then(text => {
      // now we need to parse the CSSruleSets contained
      // but chrome doesn't support styleSheets in DOMParsed docs...
      let s = document.createElement('style');
      s.innerHTML = text;
      document.head.appendChild(s);
      let styleSheet = s.sheet

      // this will help us to keep track of the rules and the original urls
      let FontRule = rule => {
        let src = rule.style.getPropertyValue('src') || rule.style.cssText.match(/url(.*?)/g)[0];
        if (!src) return null;
        let url = src.split('url(')[1].split(')')[0];
        return {
          rule: rule,
          src: src,
          url: url.replace(/"/g, '')
        };
      };
      let fontRules = [],
        fontProms = [];

      // iterate through all the cssRules of the embedded doc
      // Edge doesn't make CSSRuleList enumerable...
      for (let i = 0; i < styleSheet.cssRules.length; i++) {
        let r = styleSheet.cssRules[i];
        let fR = FontRule(r);
        if (!fR) {
          continue;
        }
        fontRules.push(fR);
        fontProms.push(
          fetch(fR.url) // fetch the actual font-file (.woff)
          .then(resp => resp.blob())
          .then(blob => {
            return new Promise(resolve => {
              // we have to return it as a dataURI
              //   because for whatever reason, 
              //   browser are afraid of blobURI in <img> too...
              let f = new FileReader();
              f.onload = e => resolve(f.result);
              f.readAsDataURL(blob);
            })
          })
          .then(dataURL => {
            // now that we have our dataURI version,
            //  we can replace the original URI with it
            //  and we return the full rule's cssText
            return fR.rule.cssText.replace(fR.url, dataURL);
          })
        )
      }
      document.head.removeChild(s); // clean up
      return Promise.all(fontProms); // wait for all this has been done
    });
}

/* Demo Code */

const ctx = canvas.getContext('2d');
let svgData = '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200">' +
  '<foreignObject width="100%" height="100%">' +
  '<div xmlns="http://www.w3.org/1999/xhtml" style="font-size:40px;font-family:Pangolin">' +
  'test' +
  '</div>' +
  '</foreignObject>' +
  '</svg>';
// I'll use a DOMParser because it's easier to do DOM manipulation for me
let svgDoc = new DOMParser().parseFromString(svgData, 'image/svg+xml');
// request our dataURI version
GFontToDataURI('https://fonts.googleapis.com/css?family=Pangolin')
  .then(cssRules => { // we've got our array with all the cssRules
    let svgNS = "http://www.w3.org/2000/svg";
    // so let's append it in our svg node
    let defs = svgDoc.createElementNS(svgNS, 'defs');
    let style = svgDoc.createElementNS(svgNS, 'style');
    style.innerHTML = cssRules.join('
');
    defs.appendChild(style);
    svgDoc.documentElement.appendChild(defs);
    // now we're good to create our string representation of the svg node
    let str = new XMLSerializer().serializeToString(svgDoc.documentElement);
    // Edge throws when blobURIs load dataURIs from https doc...
    // So we'll use only dataURIs all the way...
    let uri = 'data:image/svg+xml;charset=utf8,' + encodeURIComponent(str);

    let img = new Image();
    img.onload = function(e) {
      URL.revokeObjectURL(this.src);
      canvas.width = this.width;
      canvas.height = this.height;
      ctx.drawImage(this, 0, 0);
    }
    img.src = uri;
  })
  .catch(reason => console.log(reason)) // if something went wrong, it'll go here
<canvas id="canvas"></canvas>
另一答案

您可以尝试的第一件事是使用Google web font loader,因为您在浏览器加载字体之前生成svg

所以你需要确保加载字体然后生成svg / image

如果这不起作用,你可以在你的svg中创建文本标签,并尝试这些替代字体https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/SVG_fonts

以上是关于在SVG中绘制DOM对象时如何在Canvas中使用Google字体?的主要内容,如果未能解决你的问题,请参考以下文章

聊聊svg

SVG 与 HTML5 的 canvas 各有啥优点,哪个更有前途

Canvas和SVG区别

浅谈svg与canvas

svg和canvas的对比

Canvas和Svg有啥区别?