HTML5 canvas ctx.fillText won't do line breaks?
Solution 1
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.)
Solution 2
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<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 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;}
<canvas id="c" width="150" height="150"></canvas>
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;}
<canvas id="c" width="150" height="150"></canvas>
In the second and third examples i am using the measureText()
method which shows how long (in pixels) a string will be when printed.
Solution 3
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.
Solution 4
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;
}
}
Solution 5
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/
Spectraljump
BY DAY: Alt-Rock Ninja Cowgirl at Veridian Dynamics. BY NIGHT: I write code and code rights for penalcoders.example.org, an awesome non-profit that will totally take your money at that link. My kids are cuter than yours. FOR FUN: C+ Jokes, Segway Roller Derby, NYT Sat. Crosswords (in Sharpie!), Ostrich Grooming. "If you see scary things, look for the helpers-you'll always see people helping." -Fred Rogers
Updated on July 18, 2022Comments
-
Spectraljump almost 2 years
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.
-
Gabriele Petrioli over 13 yearsdo you want to automatically wrap when reaching the end ? or just to take into consideration the newline chars present in the text ?
-
Tower over 13 yearsWrap the text into multiple lines.
-
Tim over 13 yearsHi 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.
-
MvG almost 8 yearsPossible duplicate of HTML5 Canvas - can I somehow use linefeeds in fillText()?
-
Andrew about 4 yearsTL;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...
-
-
Tower over 13 yearsYes, that is what I am thinking of doing. It's just that with
fillText()
andstrokeText()
, you can do things beyond what CSS can do. -
Spectraljump over 13 yearsThank 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
-
SikoSoft about 13 yearsHonestly, 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.
-
Jerry Asher about 11 yearsI 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).
-
psycho brm almost 11 years
Uncaught ReferenceError: Words is not defined
If i try to change font. For example:ctx.font = '40px Arial';
- try putting that in your fiddle -
psycho brm almost 11 yearsBtw, 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.. -
jbaylina almost 11 years@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!
-
psycho brm over 10 yearsI'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 extendedArray.prototype
. Could you put it on github so that we can iterate on it? -
jbaylina over 10 years@psychobrm I merged your changes. Thank you!
-
Amol Navsupe about 9 yearshello,suppose my text is like this var text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; then what is happend in canvas???
-
couzzi over 8 yearsWorked like a charm. Thank you.
-
KaHa6uc almost 8 yearsIt will go out of the canvas, as @Ramirez did not put the maxWidth parameter to fillText :)
-
Eric Hodonsky almost 8 yearsThat'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. -
SWdV over 7 yearsSome 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! -
Simon Sarris over 7 years@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 :(
-
amir22 almost 5 yearshow to justify the whole long text ?
-
amir22 almost 5 yearshow to set direction RTL for justify?
-
Mr. Polywhirl about 4 yearsI 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. -
Geon George about 4 years@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? -
Geon George about 4 yearsEdit: it seems StackOverflow code block ignores multi spaces as well
-
GlenPeterson about 4 yearsThis library works very nicely, is small and simple, and has no dependencies. Thank you!
-
Mike 'Pomax' Kamermans almost 4 yearsIf you need a long, justified text, why would you be using a canvas?
-
Mike 'Pomax' Kamermans almost 4 yearslinks 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.
-
Summer Sun almost 4 yearsMaybe
actualBoundingBoxAscent
could be used to measure line height. -
Samnad Sainulabdeen over 2 yearsi like the third method