canvas轉圖片中的文字自動換行


概述

最近項目用到了canvas轉圖片,但是由於canvas對文字排版的支持非常弱,一般我們在canvas上畫不同排版的文字(比如豎排文字)都是利用js計算橫縱坐標,然后一個字一個字地畫出來,今天無意中看到一個使用svg的方法,記錄下來,供以后開發時參考,相信對其他人也有用。

參考資料:

SVG 簡介與截圖等應用

svg和canvas介紹

svg和canvas都支持圖形渲染,它們各有側重:

  • svg:更適用於高保真文檔,靜態圖像,可交互圖表圖形,2D游戲等。
  • canvas:更適用於屏幕截圖,視頻操作,很多對象的復雜場景,web廣告等。

foreignObject元素

foreignObject元素是個非常強大的元素,它可以在其中使用具有其它XML命名空間的XML元素,簡單來說,就是我們可以利用foreignObject元素把html“畫在”svg里面!

示例如下。其中xmlns表示XML命名空間,標記它是一個svg或者一個xhtml。注意:svg和xhtml都屬於XML。

<svg xmlns="http://www.w3.org/2000/svg">
  <foreignObject width="120" height="50">
      <body xmlns="http://www.w3.org/1999/xhtml">
        <p>文字。</p>
      </body>
    </foreignObject>
</svg>

另外,所有的svg都必須加上這個svg的命名空間標識,否則它就會以XML文檔樹的形式渲染。示例如下:

//渲染出來的是數字
<svg width="12" height="12" viewBox="0 0 12 12"><path d="M10.263 10.874L6 6.61l-4.264 4.264a.431.431 0 0 1-.61-.61L5.39 6.001 1.128 1.736a.431.431 0 0 1 .61-.61L6 5.391l4.264-4.263a.431.431 0 0 1 .61.61L6.61 6l4.262 4.264a.43.43 0 1 1-.61.609z" stroke="#979797" fill="#999"/></svg>

//渲染出來的是圖形
<svg width="12" height="12" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path d="M10.263 10.874L6 6.61l-4.264 4.264a.431.431 0 0 1-.61-.61L5.39 6.001 1.128 1.736a.431.431 0 0 1 .61-.61L6 5.391l4.264-4.263a.431.431 0 0 1 .61.61L6.61 6l4.262 4.264a.43.43 0 1 1-.61.609z" stroke="#979797" fill="#999"/></svg>

canvas中文本換行

由於我們可以利用foreignObject把html轉化為SVG,然后我們可以把svg畫在canvas里面,最后用canvas把它轉化為圖形

示例如下:

//首先初始化canvas
var canvas = document.querySelector('#canvas');
var context = canvas.getContext('2d');

//獲取文字的長度
context.font = "20px 微軟雅黑";
var textLength = context.measureText(text).width;

//畫一個svg圖片
var img = new Image();

//對文字長度做判斷,一行的文字字要大些,二行的文字字要小些
if(textLength <= 135) {
    img.src = 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="270" height="45"><body xmlns="http://www.w3.org/1999/xhtml" style="margin:0;color:#e7793f;text-align:center;font-size:40px;line-height:45px;font:微軟雅黑;">'+ text +'</body></foreignObject></svg>';
} else {
    img.src = 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="270" height="60"><body xmlns="http://www.w3.org/1999/xhtml" style="margin:0;color:#e7793f;text-align:left;font-size:25px;line-height:30px;font:微軟雅黑;overflow: hidden;display: -webkit-box;text-overflow: ellipsis;--webkit-box-orient: vertical;--webkit-line-clamp: 2;word-break: break-all;">'+ text +'</body></foreignObject></svg>';
}

//把圖片繪制上去,並利用canvas生成圖片
img.onload = function() {
    context.drawImage(img, 72, 1080);
    document.querySelector('#ticket').src = canvas.toDataURL('image/png');
}

canvas文字豎排

理論上有三種方法來解決:
1.利用js逐個字符逐行繪制,但是這樣對英文要做特殊處理,不能單個字符繪制,而應該旋轉90度。相關參考代碼如下:

fillTextVertical: function(context, text, x, y) {
  var canvas = context.canvas;

  var arrText = text.split('');
  var arrWidth = arrText.map(function (letter) {
    return context.measureText(letter).width;
  });

  var align = context.textAlign;
  var baseline = context.textBaseline;

  //emoji的位置
  var emoji_arr = fn.findEmoji(text);

  //emoji第一位的緩存
  var emoji_first = '';

  if (align == 'left') {
    x = x + Math.max.apply(null, arrWidth) / 2;
  } else if (align == 'right') {
    x = x - Math.max.apply(null, arrWidth) / 2;
  }
  if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') {
    y = y - arrWidth[0] / 2;
  } else if (baseline == 'top' || baseline == 'hanging') {
    y = y + arrWidth[0] / 2;
  }

  context.textAlign = 'center';
  context.textBaseline = 'middle';

  // 開始逐字繪制
  arrText.forEach(function (letter, index) {
    // 確定下一個字符的縱坐標位置
    var letterWidth = arrWidth[index];
    // 是否需要旋轉判斷
    var code = letter.charCodeAt(0);

    //是否是emoji的第二位
    if(emoji_arr.indexOf(index) != -1) {
      emoji_first = letter;
      return;
    }
    //是否是emoji
    if(emoji_arr.indexOf(index - 1) != -1) {
      letter = emoji_first + letter;
      emoji_first = '';
    } else {
      if (code <= 256) {
          context.translate(x, y);
          // 英文字符,旋轉90°
          context.rotate(90 * Math.PI / 180);
          context.translate(-x, -y);
      } else if (index > 0 && text.charCodeAt(index - 1) < 256) {
          // y修正
          y = y + arrWidth[index - 1] / 2;
      }
    }

    context.fillText(letter, x, y);
    // 旋轉坐標系還原成初始態
    context.setTransform(1, 0, 0, 1, 0, 0);
    // 確定下一個字符的縱坐標位置
    var letterWidth = arrWidth[index];
    y = y + letterWidth;
  });
  // 水平垂直對齊方式還原
  context.textAlign = align;
  context.textBaseline = baseline;
},

上面方法的缺點是,對emoji符號使用正則匹配的,但仍然會有部分emoji符號匹配不到導致亂碼。

2.使用foreignObject繪制。但是有一些bug,就是在安卓機上emoji表情會錯位,而且在ios上會被莫名截斷。

3.使用html2canvas庫。還是有一個bug,就是對英文的豎排支持不是很理想,英文會橫排顯示。

總之上面的方法各有各的優缺點,完美的豎排兼容emoji的實現是不可能的,這輩子都不可能的!0.0


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM