3

我对可变字体有这个问题,想知道是否有人有解决方案的想法。我已经使用可变字体构建了这个海报生成器,您可以在其中操纵两个轴上的字体变化设置。这是一个现场示例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;
}

我将不胜感激任何帮助!

4

1 回答 1

1

Unfortunately you are right, we can't at the moment use variable-fonts in a canvas directly. So this makes the canvas renderer of html2canvas unable to render that correctly.

New versions of html2canvas come with a foreignObjectRenderer, which uses the ability of the canvas API to draw SVG images, combined with the ability of SVG to contain HTML elements in a <foreignObject>.

This is indeed the only current solution we have to draw variable-fonts on a canvas, however for this to work the font needs to be embedded inside the svg document that will be drawn on the canvas. And this, html2canvas doesn't do it for us (and even though I didn't checked recently I don't think other solutions like DOM2image does that either).

So we'll have to do it ourselves.

  • First we need to fetch the font file (woff2) and encode it to a data:// URL so it can live in a standalone svg file.
  • Then we'll build the <foreignObject> element with a copy of our elements and their required computed styles.
  • Finally we'll build the svg image with the <foreignObject> and a <style> declaring our font-face from the data:// URL, and draw that on the canvas.

(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>

于 2021-01-12T03:06:14.873 回答