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

I can't seem to add text to the canvas if the text contains "\ n". I mean line breaks don't show / work.

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

The above code draws the "s ome \n <br/> thing" on one line.

Is this a limitation of fillText or am I doing it wrong? "\ n" s is and does not print, but they also do not work.

+83
javascript html5 break canvas line
Feb 17 '11 at 9:13
source share
16 answers

I'm afraid this is a restriction to fillText Canvas. There is no multi-line support. To make matters worse, there is no built-in way to measure the height of the line, only the width, which makes working on your own even more difficult!

Many people have written their own multi-line support, perhaps the most notable project Mozilla Skywriter has .

The essence of what you need to do is make multiple calls to fillText each time adding the height of the text to the y value. (I believe that measuring the width of the letter M is what people writing in the sky do to get the text closer).

+48
Feb 18 '11 at 2:42 on
source share

If you just want to take care of the newlines in the text, you can simulate it by dividing the text into newlines and calling fillText() several times

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<lines.length; i++) c.fillText(lines[i], x, y + (i*lineheight) ); 
 canvas{background-color:#ccc;} 
 <canvas id="c" width="150" height="150"></canvas> 



I just did a wrapping proof of concept (absolute wrapper over specified width. Example 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;} 
 <canvas id="c" width="150" height="150"></canvas> 



And the word wrapping (breaking in spaces) is proof of concept.
example 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;} 
 <canvas id="c" width="150" height="150"></canvas> 



In the second and third examples, I use the measureText() method, which shows how long (in pixels) the line will be printed.

+50
Dec 18 '10 at 16:23
source share

Maybe it’s coming to this party a little late, but I found the following tutorial for wrapping text on canvas.

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

From this, I was able to think about working with several lines (sorry Ramirez, yours did not work for me!). My complete code for wrapping text in 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 identifier of my canvas, and text is the identifier of my text field.

As you can see, I am using a custom font. You can use @ font-face if you used a font on some text PRIOR to manipulate the canvas - otherwise the canvas will not receive the font.

Hope this helps someone.

+38
Jul 6 2018-12-12T00:
source share

Divide 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; } } 
+22
Feb 05 '14 at 10:26
source share

Here is my solution modifying the popular wrapText () function, which is already presented here. I use the JavaScript prototype function 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; } } 

Main use:

 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 is the demo I put together: http://jsfiddle.net/7RdbL/

+17
Jul 21 '13 at 22:39
source share

Using javascript, I developed a solution. This is not 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 this helps!

+8
Nov 01 2018-11-11T00:
source share

The code for wrapping words (space- separated ) provided by @Gaby Petrioli is very useful. I expanded its code to provide support for newline characters \n . In addition, it is often useful to have bounding box sizes, so multiMeasureText() returns both width and height.

Here you can see the code: http://jsfiddle.net/jeffchan/WHgaY/76/

+7
Aug 11 '11 at 16:56
source share

I just expanded CanvasRenderingContext2D by adding two functions: mlFillText and mlStrokeText.

You can find the latest version on GitHub :

Using these functions, you can fill / move the text of miltiline into the field. You can align text vertically and horizontally. (It takes into account \ n and can also justify the text).

Prototypes:

mlFillText function (text, x, y, w, h, vAlign, hAlign, lineheight); mlStrokeText function (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 lib here: http://jsfiddle.net/4WRZj/1/

enter image description here

Here is the library code:

 // 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 a usage 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); 
+7
Feb 21 '13 at 9:54 on
source share

Here's the Colin version of wrapText() , which 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; } }; 
+4
May 09 '15 at 7:19
source share

If you need only two lines of text, you can split them into two different calls to fillText and assign 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); 
+4
Feb 12 '18 at 4:06
source share

I think you can still rely on CSS

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

Fortunately, with CSS hack-ardry (see "Typographic Metrics" for more ways to fix older implementations of using CSS dimensions), we can find the height of the text by measuring 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/

+3
May 29 '12 at 21:41
source share

I don't think this is impossible, but a workaround for this is to create a <p> element and place it using Javascript.

+2
Dec 18 '10 at 16:00
source share

As a result, I ran into this problem. I work with variable font size, so this takes this 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 edited by the user (it is embedded in jQuery of each function), and ctx.font is "14px Arial" (note that the pixel size comes first)

+2
04 Sep '13 at 21:49 on
source share

The Canvas element does not support characters such as newline '\ n', tab '\ t' or <br / ">.

Try:

 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 maybe a few 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); } 
0
Nov 03 '12 at 19:08
source share

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

Additional information about the problem in my blog .

0
May 12 '16 at 16:27
source share

I made a simple npm module for this exact use. https://www.npmjs.com/package/canvas-txt

You can split the text into several lines automatically, as well as based on \n

0
Jan 27 '19 at 17:06
source share



All Articles