【问题标题】:Variable-Fonts in html5-canvashtml5-canvas 中的可变字体
【发布时间】:2021-01-13 16:18:37
【问题描述】:

我遇到了可变字体的问题,想知道是否有人有解决方案的想法。我已经使用可变字体构建了这个海报生成器,您可以在其中操纵两个轴上的字体变化设置。这是一个活生生的例子http://automat.markjulienhahn.de

现在我正在尝试通过 html2canvas 下载结果。不幸的是,画布对象似乎不支持可变字体,因此画布对象只能显示一种字体状态,而 fontVariationSettings 没有任何效果。

这就是我拉画布元素的方式:

<script src="html2canvas.min.js"></script>    
  
<script>
    
var app = new Vue({
  el: '#app',
  methods: {
    saveCanvas(){
            html2canvas(document.querySelector("#capture")).then(
                canvas => {
                document.body.appendChild(canvas);
                var image = canvas.toDataURL("image/png").replace("image/png",  "image/octet-stream");
                console.log(image);  
                window.location.href=image;    
            });  
    }    
  }
})

</script>

这就是我操作可变字体的方式。

function randomizeState() {
    randomWeight = Math.floor(Math.random(1,100) * 100);
    randomWidth = Math.floor(Math.random(1,100) * 100);
    document.getElementById("element").style.fontVariationSettings = "\"frst\" " + randomWeight + ", \"scnd\" " + randomWidth;
    document.getElementById("state1").innerHTML = randomWeight + " " + randomWidth;
}

我将不胜感激!

【问题讨论】:

    标签: javascript canvas html5-canvas html2canvas variable-fonts


    【解决方案1】:

    很遗憾,您是对的,我们目前不能直接在画布中使用可变字体。所以这使得 html2canvas 的画布渲染器无法正确渲染。

    新版本的 html2canvas 带有 foreignObjectRenderer,它使用 canvas API 绘制 SVG 图像的能力,结合 SVG 在 &lt;foreignObject&gt; 中包含 HTML 元素的能力。

    这确实是我们必须在画布上绘制可变字体的唯一当前解决方案,但是要使其工作,字体需要嵌入到将在画布上绘制的 svg 文档中。而这一点,html2canvas 并没有为我们做这件事(尽管我最近没有检查过,但我认为像 DOM2image 这样的其他解决方案也不会这样做)。

    所以我们必须自己做。

    • 首先,我们需要获取字体文件 (woff2) 并将其编码为 data:// URL,以便它可以存在于独立的 svg 文件中。
    • 然后,我们将使用我们元素的副本和它们所需的计算样式来构建&lt;foreignObject&gt; 元素。
    • 最后,我们将使用&lt;foreignObject&gt;&lt;style&gt; 构建svg 图像,从data:// URL 声明我们的字体,并将其绘制在画布上。

    (async () => {
    
      const svgNS = "http://www.w3.org/2000/svg";
      const svg = document.createElementNS( svgNS, "svg" );
      const font_data = await fetchAsDataURL( "https://fonts.gstatic.com/s/inter/v2/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2" );
      const style = document.createElementNS( svgNS, "style" );
      style.textContent = `@font-face {
        font-family: 'Inter';
        font-style: normal;
        font-weight: 200 900;
        src: url(${ font_data }) format('woff2'); 
      }`;
      svg.append( style );
      
      const foreignObject = document.createElementNS( svgNS, "foreignObject" );
      foreignObject.setAttribute( "x", 0 );
      foreignObject.setAttribute( "y", 0 );
    
      const target = document.querySelector( ".target" );
      const clone = cloneWithStyles( target );
      foreignObject.append( clone );
      
      const { width, height } = target.getBoundingClientRect();
      foreignObject.setAttribute( "width", width );
      foreignObject.setAttribute( "height", height );
      svg.setAttribute( "width", width );
      svg.setAttribute( "height", height );
      
      svg.append( foreignObject );
      
      const svg_markup = new XMLSerializer().serializeToString( svg );
      const svg_file = new Blob( [ svg_markup ], { type: "image/svg+xml" } );
      
      const img = new Image();
      img.src = URL.createObjectURL( svg_file );
      await img.decode();
      URL.revokeObjectURL( img.src );
      
      const canvas = document.createElement( "canvas" );
      Object.assign( canvas, { width, height } );
      const ctx = canvas.getContext( "2d" );
      ctx.drawImage( img, 0, 0 );
    
      document.body.append( canvas );
      
    })().catch( console.error );
    
    
    function fetchAsDataURL( url ) {
      return fetch( url )
        .then( (resp) => resp.ok && resp.blob() )
        .then( (blob) => new Promise( (res) => {
            const reader = new FileReader();
            reader.onload = (evt) => res( reader.result );
            reader.readAsDataURL( blob );
          } )
        );
    }
    function cloneWithStyles( source ) {
      const clone = source.cloneNode( true );
      
      // to make the list of rules smaller we try to append the clone element in an iframe
      const iframe = document.createElement( "iframe" );
      document.body.append( iframe );
      // if we are in a sandboxed context it may be null
      if( iframe.contentDocument ) {
        iframe.contentDocument.body.append( clone );
      }
      
      const source_walker = document.createTreeWalker( source, NodeFilter.SHOW_ELEMENT, null );
      const clone_walker = document.createTreeWalker( clone, NodeFilter.SHOW_ELEMENT, null );
      let source_element = source_walker.currentNode;
      let clone_element = clone_walker.currentNode;
      while ( source_element ) {
      
        const source_styles = getComputedStyle( source_element );
        const clone_styles = getComputedStyle( clone_element );
    
        // we should be able to simply do [ ...source_styles.forEach( (key) => ...
        // but thanks to https://crbug.com/1073573
        // we have to filter all the snake keys from enumerable properties...
        const keys = (() => {
          // Start with a set to avoid duplicates
          const props = new Set();
          for( let prop in source_styles ) {
            // Undo camel case
            prop = prop.replace( /[A-Z]/g, (m) => "-" + m.toLowerCase() );
            // Fix vendor prefix
            prop = prop.replace( /^webkit-/, "-webkit-" );
            props.add( prop );
          }
          return props;
        })();
        for( let key of keys ) {
          if( clone_styles[ key ] !== source_styles[ key ] ) {
            clone_element.style.setProperty( key, source_styles[ key ] );
          }
        }
    
        source_element = source_walker.nextNode()
        clone_element = clone_walker.nextNode()
      
      }
      // clean up
      iframe.remove();
    
      return clone;
    }
    @font-face {
      font-family: 'Inter';
      font-style: normal;
      font-weight: 200 900;
      src: url(https://fonts.gstatic.com/s/inter/v2/UcC73FwrK3iLTeHuS_fvQtMwCp50KnMa1ZL7W0Q5nw.woff2) format('woff2');
    }
    
    .t1 {
      font-family: 'Inter';
      font-variation-settings: 'wght' 200;
    }
    .t2 {
      font-family: 'Inter';
      font-variation-settings: 'wght' 900;
    }
    
    canvas {
      border: 1px solid;
    }
    <div class="target">
      <span class="t1">
        Hello
      </span>
      <span class="t2">
        World
      </span>
    </div>

    【讨论】:

    • 效果很好,谢谢!字体周围曾经有一个笔画,用这种方法会丢失。我怎样才能将它添加到字体中。我已经尝试将它添加到 style-Variable 但它不会出现。有什么想法吗?
    • @MarkJulienHahn 我不太确定你在说什么“中风”......我现在意识到我绝对没有考虑元素的 CSS 定位,但我想它不是关于这个...
    • 是的,CSS 定位没问题。我的意思是字母周围的简单轮廓,我可以使用“-webkit-text-stroke: 1px black;”来获得。在 CSS 中。不幸的是,这在 SVG 中不起作用。
    • 哦...很奇怪,那个确实对我有用:jsfiddle.net/5yoexz4f
    • 似乎是 Safari 的问题。在 Chrome 中它工作正常。
    猜你喜欢
    • 2013-03-09
    • 2011-09-01
    • 2019-05-07
    • 1970-01-01
    • 1970-01-01
    • 2018-09-26
    • 2011-05-22
    • 2012-01-02
    • 2014-05-26
    相关资源
    最近更新 更多