<canvas> 元素中的文本换行

Posted

技术标签:

【中文标题】<canvas> 元素中的文本换行【英文标题】:Text wrap in a <canvas> element 【发布时间】:2021-12-17 20:33:49 【问题描述】:

我正在尝试使用 &lt;canvas&gt; 元素在图像上添加文本。首先绘制图像,然后在图像上绘制文本。到目前为止一切顺利。

但我面临的一个问题是,如果文本太长,它会在画布的开头和结尾处被截断。我不打算调整画布的大小,但我想知道如何将长文本包装成多行以便显示所有内容。谁能指出我正确的方向?

【问题讨论】:

fwiw, Fabric.js 在画布上有广泛的文本支持(多行、文本样式、装饰、笔画等) 【参考方案1】:

看https://developer.mozilla.org/en/Drawing_text_using_a_canvas#measureText%28%29

如果您可以看到选定的文本,并且看到它比画布宽,您可以删除单词,直到文本足够短。使用删除的单词,您可以从第二行开始并执行相同的操作。

当然,这样效率不会很高,所以你可以通过不删除一个单词来改进它,但是如果你看到文本比画布宽度宽得多,可以使用多个单词。

我没有研究,但也许他们甚至是为您执行此操作的 javascript

【讨论】:

【参考方案2】:

context.measureText(text).width 就是你要找的……

【讨论】:

【参考方案3】:

一种可能的方法(尚未完全测试,但目前效果很好)

    /**
     * Divide an entire phrase in an array of phrases, all with the max pixel length given.
     * The words are initially separated by the space char.
     * @param phrase
     * @param length
     * @return
     */
function getLines(ctx,phrase,maxPxLength,textStyle) 
    var wa=phrase.split(" "),
        phraseArray=[],
        lastPhrase=wa[0],
        measure=0,
        splitChar=" ";
    if (wa.length <= 1) 
        return wa
    
    ctx.font = textStyle;
    for (var i=1;i<wa.length;i++) 
        var w=wa[i];
        measure=ctx.measureText(lastPhrase+splitChar+w).width;
        if (measure<maxPxLength) 
            lastPhrase+=(splitChar+w);
         else 
            phraseArray.push(lastPhrase);
            lastPhrase=w;
        
        if (i===wa.length-1) 
            phraseArray.push(lastPhrase);
            break;
        
    
    return phraseArray;

【讨论】:

如果短语只包含一个单词,则返回一个空数组。 作为一个建议 - 在做了一些 perfs 之后,我发现一次测量每个字符并缓存每个字符的结果,然后将单个字符宽度加在一起,即使它看起来很昂贵,是实际上比 canvas measureText 快【参考方案4】:

这是我对它的看法...我阅读了@mizar 的答案并对其进行了一些修改...在一点帮助下我能够得到这个。

代码已删除,请参阅小提琴。

这里是示例用法。 http://jsfiddle.net/9PvMU/1/ - 这个脚本也可以看到 here 并最终成为我最终使用的......这个函数假设 ctx 在父范围内可用......如果没有,你总是可以将它传递进去。


编辑

帖子很旧,并且有我仍在修补的功能版本。到目前为止,这个版本似乎已经满足了我的需求,我希望它可以帮助其他人。


编辑

我注意到这段代码中有一个小错误。我花了一些时间来修复它,但在这里它已更新。我自己测试过,现在似乎可以正常工作了。

function fragmentText(text, maxWidth) 
    var words = text.split(' '),
        lines = [],
        line = "";
    if (ctx.measureText(text).width < maxWidth) 
        return [text];
    
    while (words.length > 0) 
        var split = false;
        while (ctx.measureText(words[0]).width >= maxWidth) 
            var tmp = words[0];
            words[0] = tmp.slice(0, -1);
            if (!split) 
                split = true;
                words.splice(1, 0, tmp.slice(-1));
             else 
                words[1] = tmp.slice(-1) + words[1];
            
        
        if (ctx.measureText(line + words[0]).width < maxWidth) 
            line += words.shift() + " ";
         else 
            lines.push(line);
            line = "";
        
        if (words.length === 0) 
            lines.push(line);
        
    
    return lines;

【讨论】:

它无法识别 'ENTER' 值。如果用户输入的长词/字符超出了允许的 maxWidth,则不会为下一个词产生空格。有什么建议吗? 它不应该识别输入值,它不是那样构建的。但是,如果您听过它,我想您可以强制换行。你是对的,我以前从未注意到这个错误。我会调查并更新我的答案。 @ivory-santos maxWidth 修剪空间现已修复。【参考方案5】:

我使用http://miteshmaheta.blogspot.sg/2012/07/html5-wrap-text-in-canvas.html这里的代码修改了它

http://jsfiddle.net/wizztjh/kDy2U/41/

【讨论】:

【参考方案6】:

@mizar 答案的更新版本,修复了一个严重错误和一个小错误。

function getLines(ctx, text, maxWidth) 
    var words = text.split(" ");
    var lines = [];
    var currentLine = words[0];

    for (var i = 1; i < words.length; i++) 
        var word = words[i];
        var width = ctx.measureText(currentLine + " " + word).width;
        if (width < maxWidth) 
            currentLine += " " + word;
         else 
            lines.push(currentLine);
            currentLine = word;
        
    
    lines.push(currentLine);
    return lines;

我们使用这段代码已经有一段时间了,但今天我们试图弄清楚为什么有些文本没有绘制,我们发现了一个错误!

事实证明,如果你给 getLines() 函数一个单词(不带任何空格),它将返回一个空数组,而不是一个单行数组。

在我们对此进行调查时,我们发现了另一个(更微妙的)错误,其中行可能会比应有的长度略长,因为原始代码在测量行长时没有考虑空格。

上面是我们的更新版本,它适用于我们投入的所有内容。如果您发现任何错误,请告诉我!

【讨论】:

我不知道这是不是你的意图,但它不处理换行符。 @Jason:这很容易解决,只需将其包装在您自己的函数中,例如:function getLinesForParagraphs(ctx, text, maxWidth) return text.split("\n").map(para =&gt; getLines(ctx, para, maxWidth)).reduce([], (a, b) =&gt; a.concat(b)) 这不适用于带有块字符的语言,例如中文。 @JunleLi:对所有语言正确地进行自动换行是很复杂的[1],但是你可以通过var words = text.split("") 对带有块字符的语言更接近,而不是在空格上拆分(请注意,这将在拉丁字母语言的单词中间分裂)。更好的解决方案是解析单词并根据语言进行拆分(因此块字符和拉丁单词可以混合并正确分解)。真正正确的解决方案需要使用字典。 [1]w3c.github.io/i18n-drafts/articles/typography/linebreak.en【参考方案7】:

来自这里的脚本:http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

我已经扩展到包括段落支持。使用 \n 换行。

function wrapText(context, text, x, y, line_width, line_height)

    var line = '';
    var paragraphs = text.split('\n');
    for (var i = 0; i < paragraphs.length; i++)
    
        var words = paragraphs[i].split(' ');
        for (var n = 0; n < words.length; n++)
        
            var testLine = line + words[n] + ' ';
            var metrics = context.measureText(testLine);
            var testWidth = metrics.width;
            if (testWidth > line_width && n > 0)
            
                context.fillText(line, x, y);
                line = words[n] + ' ';
                y += line_height;
            
            else
            
                line = testLine;
            
        
        context.fillText(line, x, y);
        y += line_height;
        line = '';
    


文本可以这样格式化:

var text = 
[
    "Paragraph 1.",
    "\n\n",
    "Paragraph 2."
].join("");

用途:

wrapText(context, text, x, y, line_width, line_height);

代替

context.fillText(text, x, y);

【讨论】:

【参考方案8】:

尝试使用此脚本将文本包裹在画布上。

 <script>
  function wrapText(ctx, text, x, y, maxWidth, lineHeight) 
    var words = text.split(' ');
    var line = '';

    for(var n = 0; n < words.length; n++) 
      var testLine = line + words[n] + ' ';
      var metrics = ctx.measureText(testLine);
      var testWidth = metrics.width;
      if (testWidth > maxWidth && n > 0) 
        ctx.fillText(line, x, y);
        line = words[n] + ' ';
        y += lineHeight;
      
      else 
        line = testLine;
      
    
    ctx.fillText(line, x, y);
  

  var canvas = document.getElementById('Canvas01');
  var ctx = canvas.getContext('2d');
  var maxWidth = 400;
  var lineHeight = 24;
  var x = (canvas.width - maxWidth) / 2;
  var y = 70;
  var text = 'HTML is the language for describing the structure of Web pages. HTML stands for HyperText Markup Language. Web pages consist of markup tags and plain text. HTML is written in the form of HTML elements consisting of tags enclosed in angle brackets (like <html>). HTML tags most commonly come in pairs like <h1> and </h1>, although some tags represent empty elements and so are unpaired, for example <img>..';

  ctx.font = '15pt Calibri';
  ctx.fillStyle = '#555555';

  wrapText(ctx, text, x, y, maxWidth, lineHeight);
  </script>
</body>

在此处查看演示 http://codetutorial.com/examples-canvas/canvas-examples-text-wrap。

【讨论】:

【参考方案9】:

这应该会从文本框中正确显示行:-

 function fragmentText(text, maxWidth) 
    var lines = text.split("\n");
    var fittingLines = [];
    for (var i = 0; i < lines.length; i++) 
        if (canvasContext.measureText(lines[i]).width <= maxWidth) 
            fittingLines.push(lines[i]);
        
        else 
            var tmp = lines[i];
            while (canvasContext.measureText(tmp).width > maxWidth) 
                tmp = tmp.slice(0, tmp.length - 1);
            
            if (tmp.length >= 1) 
                var regex = new RegExp(".1," + tmp.length + "", "g");
                var thisLineSplitted = lines[i].match(regex);
                for (var j = 0; j < thisLineSplitted.length; j++) 
                    fittingLines.push(thisLineSplitted[j]);
                
            
        
    
    return fittingLines;

然后在画布上绘制提取的线条:-

 var lines = fragmentText(textBoxText, (rect.w - 10)); //rect.w = canvas width, rect.h = canvas height
                    for (var showLines = 0; showLines < lines.length; showLines++)  // do not show lines that go beyond the height
                        if ((showLines * resultFont.height) >= (rect.h - 10))       // of the canvas
                            break;
                        
                    
                    for (var i = 1; i <= showLines; i++) 
                        canvasContext.fillText(lines[i-1], rect.clientX +5 , rect.clientY + 10 + (i * (resultFont.height))); // resultfont = get the font height using some sort of calculation
                    

【讨论】:

【参考方案10】:

我发布了我自己使用的版本here,因为这里的答案对我来说还不够。在我的情况下,第一个词需要测量,以便能够从小画布区域否认太长的词。而且我需要对“break+space”、“space+break”或双换行符/段落换行符组合的支持。

wrapLines: function(ctx, text, maxWidth) 
    var lines = [],
        words = text.replace(/\n\n/g,' ` ').replace(/(\n\s|\s\n)/g,'\r')
        .replace(/\s\s/g,' ').replace('`',' ').replace(/(\r|\n)/g,' '+' ').split(' '),
        space = ctx.measureText(' ').width,
        width = 0,
        line = '',
        word = '',
        len = words.length,
        w = 0,
        i;
    for (i = 0; i < len; i++) 
        word = words[i];
        w = word ? ctx.measureText(word).width : 0;
        if (w) 
            width = width + space + w;
        
        if (w > maxWidth) 
            return [];
         else if (w && width < maxWidth) 
            line += (i ? ' ' : '') + word;
         else 
            !i || lines.push(line !== '' ? line.trim() : '');
            line = word;
            width = w;
        
    
    if (len !== i || line !== '') 
        lines.push(line);
    
    return lines;

它支持换行符或分段符的任何变体,删除双空格以及开头或结尾的分段符。如果文本不合适,它会返回一个空数组。或者准备绘制的线数组。

【讨论】:

以上是关于<canvas> 元素中的文本换行的主要内容,如果未能解决你的问题,请参考以下文章

Canvas

canvas文本自动换行

围绕特定文本换行新的div元素

element中form表单验证左边的字体换行了

如何可以让div内的元素不换行?

微信小程序-canvas绘制文字实现自动换行