thing"。这是 fillText 的限制还是我做错了? "\n" 在那里,并且没有打印出来,但它们也不起作用。" /> thing"。这是 fillText 的限制还是我做错了? "\n" 在那里,并且没有打印出来,但它们也不起作用。"> thing"。这是 fillText 的限制还是我做错了? "\n" 在那里,并且没有打印出来,但它们也不起作用。" />
ChatGPT解决这个技术问题 Extra ChatGPT

HTML5 canvas ctx.fillText won't do line breaks?

I can't seem to be able to add text to a canvas if the text includes "\n". I mean, the line breaks do not show/work.

ctxPaint.fillText("s  ome \n \\n <br/> thing", x, y);

The above code will draw "s ome \n <br/> thing", on one line.

Is this a limitation of fillText or am I doing it wrong? the "\n"s are there, and aren't printed, but they don't work either.

do you want to automatically wrap when reaching the end ? or just to take into consideration the newline chars present in the text ?
Wrap the text into multiple lines.
Hi twodordan, does this limitation exist on both chrome and mozilla ? People often use simple html text that they put over the canvas with a position:absolute for example. Also you can do two fillText and moving the Y origin of your text for your second lines.
TL;DR: Either call fillText() multiple times and use your font height to separate, or use developer.mozilla.org/en-US/docs/Web/API/TextMetrics developer.mozilla.org/en-US/docs/Web/API/… - or, use one of the very complicated "solutions" below that do not use TextMetrics...

G
Gabriele Petrioli

If you just want to take care of the newline chars in the text you could simulate it by splitting the text at the newlines and calling multiple times the fillText()

Something like http://jsfiddle.net/BaG4J/1/

var c = document.getElementById('c').getContext('2d'); c.font = '11px Courier'; console.log(c); var txt = 'line 1\nline 2\nthird line..'; var x = 30; var y = 30; var lineheight = 15; var lines = txt.split('\n'); for (var i = 0; i

I just made a wrapping proof of concept (absolute wrap at specified width. No handling words breaking, yet)
example at http://jsfiddle.net/BaG4J/2/

var c = document.getElementById('c').getContext('2d'); c.font = '11px Courier'; var txt = 'this is a very long text to print'; printAt(c, txt, 10, 20, 15, 90 ); function printAt( context , text, x, y, lineHeight, fitWidth) { fitWidth = fitWidth || 0; if (fitWidth <= 0) { context.fillText( text, x, y ); return; } for (var idx = 1; idx <= text.length; idx++) { var str = text.substr(0, idx); console.log(str, context.measureText(str).width, fitWidth); if (context.measureText(str).width > fitWidth) { context.fillText( text.substr(0, idx-1), x, y ); printAt(context, text.substr(idx-1), x, y + lineHeight, lineHeight, fitWidth); return; } } context.fillText( text, x, y ); } canvas{background-color:#ccc;}

And a word-wrapping (breaking at spaces) proof of concept.
example at http://jsfiddle.net/BaG4J/5/

var c = document.getElementById('c').getContext('2d'); c.font = '11px Courier'; var txt = 'this is a very long text. Some more to print!'; printAtWordWrap(c, txt, 10, 20, 15, 90 ); function printAtWordWrap( context , text, x, y, lineHeight, fitWidth) { fitWidth = fitWidth || 0; if (fitWidth <= 0) { context.fillText( text, x, y ); return; } var words = text.split(' '); var currentLine = 0; var idx = 1; while (words.length > 0 && idx <= words.length) { var str = words.slice(0,idx).join(' '); var w = context.measureText(str).width; if ( w > fitWidth ) { if (idx==1) { idx=2; } context.fillText( words.slice(0,idx-1).join(' '), x, y + (lineHeight*currentLine) ); currentLine++; words = words.splice(idx-1); idx = 1; } else {idx++;} } if (idx > 0) context.fillText( words.join(' '), x, y + (lineHeight*currentLine) ); } canvas{background-color:#ccc;}

In the second and third examples i am using the measureText() method which shows how long (in pixels) a string will be when printed.


how to justify the whole long text ?
If you need a long, justified text, why would you be using a canvas?
i like the third method
N
Neuron

I'm afraid it is a limitation of Canvas' fillText. There is no multi-line support. Whats worse, there's no built-in way to measure line height, only width, making doing it yourself even harder!

A lot of people have written their own multi-line support, perhaps the most notable project that has is Mozilla Skywriter.

The gist of what you'll need to do is multiple fillText calls while adding the height of the text to the y value each time. (measuring the width of M is what the skywriter people do to approximate text, I believe.)


Thank you! I had a feeling it would be bothersome... Nice to know about the SKYWRITER, but I'll just "wait" until fillText() is improved. It wasn't a terribly important deal in my case. Hah, no line height, it's like someone did that on purpose. :D
Honestly, I wouldn't hold your breath on fillText() being "improved" to support this, since I get the feeling this is how it is intended to be used (multiple calls & calculating the yOffset yourself). I think a lot of the power with the canvas API is that it separates the lower-level drawing functionality from what you can already do (perform the necessary measurements). Also, you can know the text height simply by providing the text size in pixels; in other words: context.font = "16px Arial"; - you have the height there; the width is the only one that is dynamic.
Some additional properties for measureText() have been added which I think could solve the problem. Chrome has a flag to enable them, but other browsers don't... yet!
@SWdV just to be clear, those have been in the spec for years now, it may be years yet until we have wide enough adoption to use :(
Maybe actualBoundingBoxAscent could be used to measure line height.
i
ivstas

Maybe coming to this party a bit late, but I found the following tutorial for wrapping text on a canvas perfect.

http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

From that I was able to think get multi lines working (sorry Ramirez, yours didn't work for me!). My complete code to wrap text in a canvas is as follows:

<script type="text/javascript">

     // http: //www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/
     function wrapText(context, text, x, y, maxWidth, lineHeight) {
        var cars = text.split("\n");

        for (var ii = 0; ii < cars.length; ii++) {

            var line = "";
            var words = cars[ii].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 > maxWidth) {
                    context.fillText(line, x, y);
                    line = words[n] + " ";
                    y += lineHeight;
                }
                else {
                    line = testLine;
                }
            }

            context.fillText(line, x, y);
            y += lineHeight;
        }
     }

     function DrawText() {

         var canvas = document.getElementById("c");
         var context = canvas.getContext("2d");

         context.clearRect(0, 0, 500, 600);

         var maxWidth = 400;
         var lineHeight = 60;
         var x = 20; // (canvas.width - maxWidth) / 2;
         var y = 58;


         var text = document.getElementById("text").value.toUpperCase();                

         context.fillStyle = "rgba(255, 0, 0, 1)";
         context.fillRect(0, 0, 600, 500);

         context.font = "51px 'LeagueGothicRegular'";
         context.fillStyle = "#333";

         wrapText(context, text, x, y, maxWidth, lineHeight);
     }

     $(document).ready(function () {

         $("#text").keyup(function () {
             DrawText();
         });

     });

    </script>

Where c is the ID of my canvas and text is the ID of my textbox.

As you can probably see am using a non-standard font. You can use @font-face as long as you have used the font on some text PRIOR to manipulating the canvas - otherwise the canvas won't pick up the font.

Hope this helps someone.


R
Rok Strniša

Split the text into lines, and draw each separately:

function fillTextMultiLine(ctx, text, x, y) {
  var lineHeight = ctx.measureText("M").width * 1.2;
  var lines = text.split("\n");
  for (var i = 0; i < lines.length; ++i) {
    ctx.fillText(lines[i], x, y);
    y += lineHeight;
  }
}

J
Jake

Here's my solution, modifying the popular wrapText() function that is already presented here. I'm using the prototyping feature of JavaScript so that you can call the function from the canvas context.

CanvasRenderingContext2D.prototype.wrapText = function (text, x, y, maxWidth, lineHeight) {

    var lines = text.split("\n");

    for (var i = 0; i < lines.length; i++) {

        var words = lines[i].split(' ');
        var line = '';

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

        this.fillText(line, x, y);
        y += lineHeight;
    }
}

Basic usage:

var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
ctx.fillStyle = "black";
ctx.font = "12px sans-serif";
ctx.textBaseline = "top";
ctx.wrapText("Hello\nWorld!",20,20,160,16);

Here's a demonstration I put together: http://jsfiddle.net/7RdbL/


Worked like a charm. Thank you.
R
Rifky Niyas

I just extended the CanvasRenderingContext2D adding two functions: mlFillText and mlStrokeText.

You can find the last version in GitHub:

With this functions you can fill / stroke miltiline text in a box. You can align the text verticaly and horizontaly. (It takes in account \n's and can also justify the text).

The prototypes are:

function mlFillText(text,x,y,w,h,vAlign,hAlign,lineheight);
function mlStrokeText(text,x,y,w,h,vAlign,hAlign,lineheight);

Where vAlign can be: top, center or button

And hAlign can be: left, center, right or justify

You can test the lib here: http://jsfiddle.net/4WRZj/1/

https://i.stack.imgur.com/QIITC.png

Here is the code of the library:

// Library: mltext.js
// Desciption: Extends the CanvasRenderingContext2D that adds two functions: mlFillText and mlStrokeText.
//
// The prototypes are: 
//
// function mlFillText(text,x,y,w,h,vAlign,hAlign,lineheight);
// function mlStrokeText(text,x,y,w,h,vAlign,hAlign,lineheight);
// 
// Where vAlign can be: "top", "center" or "button"
// And hAlign can be: "left", "center", "right" or "justify"
// Author: Jordi Baylina. (baylina at uniclau.com)
// License: GPL
// Date: 2013-02-21

function mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, fn) {
    text = text.replace(/[\n]/g, " \n ");
    text = text.replace(/\r/g, "");
    var words = text.split(/[ ]+/);
    var sp = this.measureText(' ').width;
    var lines = [];
    var actualline = 0;
    var actualsize = 0;
    var wo;
    lines[actualline] = {};
    lines[actualline].Words = [];
    i = 0;
    while (i < words.length) {
        var word = words[i];
        if (word == "\n") {
            lines[actualline].EndParagraph = true;
            actualline++;
            actualsize = 0;
            lines[actualline] = {};
            lines[actualline].Words = [];
            i++;
        } else {
            wo = {};
            wo.l = this.measureText(word).width;
            if (actualsize === 0) {
                while (wo.l > w) {
                    word = word.slice(0, word.length - 1);
                    wo.l = this.measureText(word).width;
                }
                if (word === "") return; // I can't fill a single character
                wo.word = word;
                lines[actualline].Words.push(wo);
                actualsize = wo.l;
                if (word != words[i]) {
                    words[i] = words[i].slice(word.length, words[i].length);
                } else {
                    i++;
                }
            } else {
                if (actualsize + sp + wo.l > w) {
                    lines[actualline].EndParagraph = false;
                    actualline++;
                    actualsize = 0;
                    lines[actualline] = {};
                    lines[actualline].Words = [];
                } else {
                    wo.word = word;
                    lines[actualline].Words.push(wo);
                    actualsize += sp + wo.l;
                    i++;
                }
            }
        }
    }
    if (actualsize === 0) lines[actualline].pop();
    lines[actualline].EndParagraph = true;

    var totalH = lineheight * lines.length;
    while (totalH > h) {
        lines.pop();
        totalH = lineheight * lines.length;
    }

    var yy;
    if (vAlign == "bottom") {
        yy = y + h - totalH + lineheight;
    } else if (vAlign == "center") {
        yy = y + h / 2 - totalH / 2 + lineheight;
    } else {
        yy = y + lineheight;
    }

    var oldTextAlign = this.textAlign;
    this.textAlign = "left";

    for (var li in lines) {
        var totallen = 0;
        var xx, usp;
        for (wo in lines[li].Words) totallen += lines[li].Words[wo].l;
        if (hAlign == "center") {
            usp = sp;
            xx = x + w / 2 - (totallen + sp * (lines[li].Words.length - 1)) / 2;
        } else if ((hAlign == "justify") && (!lines[li].EndParagraph)) {
            xx = x;
            usp = (w - totallen) / (lines[li].Words.length - 1);
        } else if (hAlign == "right") {
            xx = x + w - (totallen + sp * (lines[li].Words.length - 1));
            usp = sp;
        } else { // left
            xx = x;
            usp = sp;
        }
        for (wo in lines[li].Words) {
            if (fn == "fillText") {
                this.fillText(lines[li].Words[wo].word, xx, yy);
            } else if (fn == "strokeText") {
                this.strokeText(lines[li].Words[wo].word, xx, yy);
            }
            xx += lines[li].Words[wo].l + usp;
        }
        yy += lineheight;
    }
    this.textAlign = oldTextAlign;
}

(function mlInit() {
    CanvasRenderingContext2D.prototype.mlFunction = mlFunction;

    CanvasRenderingContext2D.prototype.mlFillText = function (text, x, y, w, h, vAlign, hAlign, lineheight) {
        this.mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, "fillText");
    };

    CanvasRenderingContext2D.prototype.mlStrokeText = function (text, x, y, w, h, vAlign, hAlign, lineheight) {
        this.mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, "strokeText");
    };
})();

And here is the use example:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var T = "This is a very long line line with a CR at the end.\n This is the second line.\nAnd this is the last line.";
var lh = 12;

ctx.lineWidth = 1;

ctx.mlFillText(T, 10, 10, 100, 100, 'top', 'left', lh);
ctx.strokeRect(10, 10, 100, 100);

ctx.mlFillText(T, 110, 10, 100, 100, 'top', 'center', lh);
ctx.strokeRect(110, 10, 100, 100);

ctx.mlFillText(T, 210, 10, 100, 100, 'top', 'right', lh);
ctx.strokeRect(210, 10, 100, 100);

ctx.mlFillText(T, 310, 10, 100, 100, 'top', 'justify', lh);
ctx.strokeRect(310, 10, 100, 100);

ctx.mlFillText(T, 10, 110, 100, 100, 'center', 'left', lh);
ctx.strokeRect(10, 110, 100, 100);

ctx.mlFillText(T, 110, 110, 100, 100, 'center', 'center', lh);
ctx.strokeRect(110, 110, 100, 100);

ctx.mlFillText(T, 210, 110, 100, 100, 'center', 'right', lh);
ctx.strokeRect(210, 110, 100, 100);

ctx.mlFillText(T, 310, 110, 100, 100, 'center', 'justify', lh);
ctx.strokeRect(310, 110, 100, 100);

ctx.mlFillText(T, 10, 210, 100, 100, 'bottom', 'left', lh);
ctx.strokeRect(10, 210, 100, 100);

ctx.mlFillText(T, 110, 210, 100, 100, 'bottom', 'center', lh);
ctx.strokeRect(110, 210, 100, 100);

ctx.mlFillText(T, 210, 210, 100, 100, 'bottom', 'right', lh);
ctx.strokeRect(210, 210, 100, 100);

ctx.mlFillText(T, 310, 210, 100, 100, 'bottom', 'justify', lh);
ctx.strokeRect(310, 210, 100, 100);

ctx.mlStrokeText("Yo can also use mlStrokeText!", 0 , 310 , 420, 30, 'center', 'center', lh);

Uncaught ReferenceError: Words is not defined If i try to change font. For example: ctx.font = '40px Arial'; - try putting that in your fiddle
Btw, where the hell does Words (case-sensitive) variable come from?? It's not defined anywhere. That part of the code only gets executed when you change font..
@psychobrm You are absolutly right. It is a bug (I already fix it). This part of code is only executed if you have to split a word in two lines. Thank you!
I've made some upgrades I needed: render spaces, render leading/trailing newlines, render stroke and fill with one call (dont measure text twice), I've also had to change iteration, since for in doesn't work well with extended Array.prototype. Could you put it on github so that we can iterate on it?
@psychobrm I merged your changes. Thank you!
M
Mr. Polywhirl

I created a tiny library for this scenario here: Canvas-Txt

It renders text in multi-line and it offers decent alignment modes.

https://i.stack.imgur.com/9x66i.jpg

In order to use this, you will need to either install it or use a CDN.

Installation

npm install canvas-txt --save

JavaScript

import canvasTxt from 'canvas-txt'

var c = document.getElementById('myCanvas')
var ctx = c.getContext('2d')

var txt = 'Lorem ipsum dolor sit amet'

canvasTxt.fontSize = 24

canvasTxt.drawText(ctx, txt, 100, 200, 200, 200)

This will render text in an invisible box with position/dimensions of:

{ x: 100, y: 200, height: 200, width: 200 }

Example Fiddle

/* https://github.com/geongeorge/Canvas-Txt */ const canvasTxt = window.canvasTxt.default; const ctx = document.getElementById('myCanvas').getContext('2d'); const txt = "Lorem ipsum dolor sit amet"; const bounds = { width: 240, height: 80 }; let origin = { x: ctx.canvas.width / 2, y: ctx.canvas.height / 2, }; let offset = { x: origin.x - (bounds.width / 2), y: origin.y - (bounds.height / 2) }; canvasTxt.fontSize = 20; ctx.fillStyle = '#C1A700'; ctx.fillRect(offset.x, offset.y, bounds.width, bounds.height); ctx.fillStyle = '#FFFFFF'; canvasTxt.drawText(ctx, txt, offset.x, offset.y, bounds.width, bounds.height); body { background: #111; } canvas { border: 1px solid #333; background: #222; /* Could alternatively be painted on the canvas */ }


I went ahead and defined some variables to help "self-document" the example. It also handles centering the bounding box within the canvas. I also added a rectangle behind, so you can actually see it centered in relation. Great work! +1 One little thing I noticed is that the lines that wrap will not have their leading spaces suppressed. You may want to trim each line e.g. ctx.fillText(txtline.trim(), textanchor, txtY) I only noticed this in your interactive demo on your website.
@Mr.Polywhirl Thank you for clearing up the answer. I have fixed the trim issue and published the 2.0.9 version. The demo site is fixed by updating the package version. There is an issue with multiple spaces. I do not know if it's better to go with an opinionated package or ignore the problem. Been getting requests for this from multiple places. I went ahead and added trim anyway. Lorem ipsum dolor, sit <many spaces> amet this was the reason why I didn't do it in the first place. What do you think should I consider multiple spaces and only remove if there is just one?
Edit: it seems StackOverflow code block ignores multi spaces as well
R
Ramirez

Using javascript I developed a solution. It isn't beautiful but it worked for me:

function drawMultilineText(){

    // set context and formatting   
    var context = document.getElementById("canvas").getContext('2d');
    context.font = fontStyleStr;
    context.textAlign = "center";
    context.textBaseline = "top";
    context.fillStyle = "#000000";

    // prepare textarea value to be drawn as multiline text.
    var textval = document.getElementByID("textarea").value;
    var textvalArr = toMultiLine(textval);
    var linespacing = 25;
    var startX = 0;
    var startY = 0;

    // draw each line on canvas. 
    for(var i = 0; i < textvalArr.length; i++){
        context.fillText(textvalArr[i], x, y);
        y += linespacing;
    }
}

// Creates an array where the <br/> tag splits the values.
function toMultiLine(text){
   var textArr = new Array();
   text = text.replace(/\n\r?/g, '<br/>');
   textArr = text.split("<br/>");
   return textArr;
}

Hope that helps!


hello,suppose my text is like this var text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; then what is happend in canvas???
It will go out of the canvas, as @Ramirez did not put the maxWidth parameter to fillText :)
C
Community

The code for word-wrapping (breaking at spaces) provided by @Gaby Petrioli is very helpful. I've extended his code to provide support for newline characters \n. Also, often times it's useful to have the dimensions of the bounding box, so multiMeasureText() returns both the width and the height.

You can see the code here: http://jsfiddle.net/jeffchan/WHgaY/76/


links expire, please put the code in this answer even if you have a working link. If jsfiddle shuts down, this answer becomes entirely useless as is.
T
Tom Söderlund

Here's a version of Colin's wrapText() that also supports vertically centered text with context.textBaseline = 'middle':

var wrapText = function (context, text, x, y, maxWidth, lineHeight) {
    var paragraphs = text.split("\n");
    var textLines = [];

    // Loop through paragraphs
    for (var p = 0; p < paragraphs.length; p++) {
        var line = "";
        var words = paragraphs[p].split(" ");
        // Loop through words
        for (var w = 0; w < words.length; w++) {
            var testLine = line + words[w] + " ";
            var metrics = context.measureText(testLine);
            var testWidth = metrics.width;
            // Make a line break if line is too long
            if (testWidth > maxWidth) {
                textLines.push(line.trim());
                line = words[w] + " ";
            }
            else {
                line = testLine;
            }
        }
        textLines.push(line.trim());
    }

    // Move text up if centered vertically
    if (context.textBaseline === 'middle')
        y = y - ((textLines.length-1) * lineHeight) / 2;

    // Render text on canvas
    for (var tl = 0; tl < textLines.length; tl++) {
        context.fillText(textLines[tl], x, y);
        y += lineHeight;
    }
};

t
thingEvery

If you only need two lines of text, you can split them into two different fillText calls and give each one a different baseline.

ctx.textBaseline="bottom";
ctx.fillText("First line", x-position, y-position);
ctx.textBaseline="top";
ctx.fillText("Second line", x-position, y-position);

j
jayjey

This question isn't thinking in terms of how canvas works. If you want a line break just simply adjust the coordinates of your next ctx.fillText.

ctx.fillText("line1", w,x,y,z)
ctx.fillText("line2", w,x,y,z+20)

D
Daniele B

I think you can still rely on CSS

ctx.measureText().height doesn’t exist.

Luckily, through CSS hack-ardry ( seeTypographic Metrics for more ways to fix older implementations of using CSS measurements), we can find the height of the text through measuring the offsetHeight of a with the same font-properties:

var d = document.createElement(”span”);
d.font = “20px arial”
d.textContent = “Hello world!”
var emHeight = d.offsetHeight;

from: http://www.html5rocks.com/en/tutorials/canvas/texteffects/


That's a good solution if you have the memory to build an element like that every time you need to measure. You can also ctx.save() then, ctx.font = '12pt Arial' then, parseInt( ctx.font, 10 ). Note that I use 'pt' when setting it. It then will translate into PX and be able to turn into a digit for consumption as the height of the font.
S
Steve Landey

There is a well-maintained JS library called Canvas-Txt that is able to handle line breaks and text wrapping. I found it much more usable than all the random code snippets in this thread.


H
Harmen

I don't think this is possible neither, but a workaround for this is to create a <p>element and position it with Javascript.


Yes, that is what I am thinking of doing. It's just that with fillText() and strokeText(), you can do things beyond what CSS can do.
I haven't tested this, but I think this may be a better solution -- the other solutions here using fillText() make it so the text cannot be selected (or presumably pasted).
M
MaKR

I happened across this due to having the same problem. I'm working with variable font size, so this takes that into account:

var texts=($(this).find('.noteContent').html()).split("<br>");
for (var k in texts) {
    ctx.fillText(texts[k], left, (top+((parseInt(ctx.font)+2)*k)));
}

where .noteContent is the contenteditable div the user edited (this is nested in a jQuery each function), and ctx.font is "14px Arial" (notice that the pixel size comes first)


V
Văn Quyết

I have created the wraptext library that helps wrap text to fit the limitations of the view.

To wrap the text, we need to implement 2 logic:

Line Fitting: Make sure no lines exceed the view width. This can be done by using Canvas API to measure the text width.

Line Breaking: Make sure the lines are broken at the right positions (line break opportunities). This logic may be implemented differently, but it is better to follow this Unicode's document as it supports all languages.

Though this answer is too far from the moment the question was posted, I hope this will be helpful for those who have the same issue.


L
Linh

Here is my function to draw multiple lines of text center in canvas (only break the line, don't break-word)

var c = document.getElementById("myCanvas"); var ctx = c.getContext("2d"); let text = "Hello World \n Hello World 2222 \n AAAAA \n thisisaveryveryveryveryveryverylongword. " ctx.font = "20px Arial"; fillTextCenter(ctx, text, 0, 0, c.width, c.height) function fillTextCenter(ctx, text, x, y, width, height) { ctx.textBaseline = 'middle'; ctx.textAlign = "center"; const lines = text.match(/[^\r\n]+/g); for(let i = 0; i < lines.length; i++) { let xL = (width - x) / 2 let yL = y + (height / (lines.length + 1)) * (i+1) ctx.fillText(lines[i], xL, yL) } }

If you want to fit text size to canvas, you can also check here


D
Dariusz J

Canvas element doesn't support such characters as newline '\n', tab '\t' or < br /> tag.

Try it:

var newrow = mheight + 30;
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = "bold 24px 'Verdana'";
ctx.textAlign = "center";
ctx.fillText("Game Over", mwidth, mheight); //first line
ctx.fillText("play again", mwidth, newrow); //second line 

or perhaps multiple lines:

var textArray = new Array('line2', 'line3', 'line4', 'line5');
var rows = 98;
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = "bold 24px 'Verdana'";
ctx.textAlign = "center";
ctx.fillText("Game Over", mwidth, mheight); //first line

for(var i=0; i < textArray.length; ++i) {
rows += 30;
ctx.fillText(textArray[i], mwidth, rows); 
}  

O
Oleg Berman

My ES5 solution for the problem:

var wrap_text = (ctx, text, x, y, lineHeight, maxWidth, textAlign) => {
  if(!textAlign) textAlign = 'center'
  ctx.textAlign = textAlign
  var words = text.split(' ')
  var lines = []
  var sliceFrom = 0
  for(var i = 0; i < words.length; i++) {
    var chunk = words.slice(sliceFrom, i).join(' ')
    var last = i === words.length - 1
    var bigger = ctx.measureText(chunk).width > maxWidth
    if(bigger) {
      lines.push(words.slice(sliceFrom, i).join(' '))
      sliceFrom = i
    }
    if(last) {
      lines.push(words.slice(sliceFrom, words.length).join(' '))
      sliceFrom = i
    }
  }
  var offsetY = 0
  var offsetX = 0
  if(textAlign === 'center') offsetX = maxWidth / 2
  for(var i = 0; i < lines.length; i++) {
    ctx.fillText(lines[i], x + offsetX, y + offsetY)
    offsetY = offsetY + lineHeight
  }
}

More information on the issue is on my blog.