Javascript <canvas> 元素中的文本换行

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2936112/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 02:34:21  来源:igfitidea点击:

Text wrap in a <canvas> element

javascripthtmlcanvastext

提问by Gwood

I am trying to add text on an image using the <canvas>element. First the image is drawn and on the image the text is drawn. So far so good.

我正在尝试使用该<canvas>元素在图像上添加文本。首先绘制图像,然后在图像上绘制文本。到现在为止还挺好。

But where I am facing a problem is that if the text is too long, it gets cut off in the start and end by the canvas. I don't plan to resize the canvas, but I was wondering how to wrap the long text into multiple lines so that all of it gets displayed. Can anyone point me at the right direction?

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

回答by crazy2be

Updated version of @mizar's answer, with one severe and one minor bug fixed.

@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;
}

We've been using this code for some time, but today we were trying to figure out why some text wasn't drawing, and we found a bug!

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

It turns out that if you give a single word (without any spaces) to the getLines() function, it will return an empty array, rather than an array with a single line.

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

While we were investigating that, we found another (much more subtle) bug, where lines can end up slightly longer than they should be, since the original code didn't account for spaces when measuring the length of a line.

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

Our updated version, which works for everything we've thrown at it, is above. Let me know if you find any bugs!

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

回答by mizar

A possible method (not completely tested, but as for now it worked perfectly)

一种可能的方法(未完全测试,但就目前而言,它运行良好)

    /**
     * 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;
}

回答by rlemon

Here was my spin on it... I read @mizar's answer and made some alterations to it... and with a little assistance I Was able to get this.

这是我的看法……我阅读了@mizar 的回答并对其进行了一些修改……在一些帮助下,我得到了这个。

code removed, see fiddle.

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

Here is example usage. http://jsfiddle.net/9PvMU/1/- this script can also be seen hereand ended up being what I used in the end... this function assumes ctxis available in the parent scope... if not you can always pass it in.

这是示例用法。http://jsfiddle.net/9PvMU/1/- 这个脚本也可以在这里看到并最终成为我最后使用的......这个函数假设ctx在父作用域中可用......如果不是你总是可以传进去。



edit

编辑

the post was old and had my version of the function that I was still tinkering with. This version seems to have met my needs thus far and I hope it can help anyone else.

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



edit

编辑

It was brought to my attention there was a small bug in this code. It took me some time to get around to fixing it but here it is updated. I have tested it myself and it seems to work as expected now.

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

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;
}

回答by Warty

context.measureText(text).widthis what you're looking for...

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

回答by squarcle

From the script here: http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

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

I've extended to include paragraph support. Use \n for new line.

我已经扩展到包括段落支持。使用 \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 = '';
    }
}


Text can be formatted like so:

文本可以像这样格式化:

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


Use:

用:

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

in place of

代替

context.fillText(text, x, y);

回答by MichaelCalvin

Try this script to wrap the text on a canvas.

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

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

See demo here http://codetutorial.com/examples-canvas/canvas-examples-text-wrap.

在这里查看演示 http://codetutorial.com/examples-canvas/canvas-examples-text-wrap

回答by Nathan

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

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

If you can see the selected text, and see its wider than your canvas, you can remove words, until the text is short enough. With the removed words, you can start at the second line and do the same.

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

Of course, this will not be very efficient, so you can improve it by not removing one word, but multiple words if you see the text is much wider than the canvas width.

当然,这不会很有效,因此如果您看到文本比画布宽度宽得多,您可以通过不删除一个单词来改进它,而是删除多个单词。

I did not research, but maybe their are even javascript libraries that do this for you

我没有研究,但也许他们甚至是为你做这件事的 javascript 库

回答by JBelfort

This should bring the lines correctly from the textbox:-

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

 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;

And then get draw the fetched lines on the canvas :-

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

 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
                    }

回答by hexalys

I am posting my own version used heresince answers here weren't insufficient for me. The first word needed to be measured in my case, to be able to deny too long words from small canvas areas. And I needed support for 'break+space, 'space+break' or double-break/paragraph-break combos.

我在这里发布我自己使用的版本因为这里的答案对我来说还不够。在我的情况下,需要衡量第一个词,以便能够拒绝来自小画布区域的太长的词。而且我需要支持“break+space”、“space+break”或double-break/paragraph-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;
}

It supports any variants of lines breaks, or paragraph breaks, removes double spaces, as well as leading or trailing paragraph breaks. It returns either an empty array if the text doesn't fit. Or an array of lines ready to draw.

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