Since there is not a proper text-overflow mechanism for Canvas we need to create our own. In this post I will show you my implementation of Canvas text-overflow and how to handle it.
First, here is my complete function to handle text-overflow and the Canvas element:
function fragmentText(text, maxWidth) {
var words = text.split(' '),
lines = [],
line = "";
if (ctx.measureText(text).width < maxWidth) {
return [text];
}
while (words.length > 0) {
while (ctx.measureText(words[0]).width >= maxWidth) {
var tmp = words[0];
words[0] = tmp.slice(0, -1);
if (words.length > 1) {
words[1] = tmp.slice(-1) + words[1];
} else {
words.push(tmp.slice(-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;
}
Ok, that is probably confusing to you (if it is not you can stop reading now), I will try to explain what the method is doing.
I choose to split the sentence up into individual words, then act on those words and rebuild the sentence back up based on the maxWidth parameter you pass in.
The first thing is to check if the sentence even needs to be split up. If it does not then return the sentence as a single line.
if (ctx.measureText(text).width < maxWidth) {
return [text];
}
If you are a stickler for performance you should write the check before you define any local variables in the function scope, however in most cases this does not make any measurable difference.
The next bit of code does the actual text splitting. The outer while (){} is looping over all words, checking to see if adding the next word to the line will make the line wider than the maxWidth, and if so creating a new line from it. The inner while (){} is checking each word to make sure it in itself is not longer than the maxWidth, and if it is breaking the word up into multiple words.
while (words.length > 0) {
while (ctx.measureText(words[0]).width >= maxWidth) {
var tmp = words[0];
words[0] = tmp.slice(0, -1);
if (words.length > 1) {
words[1] = tmp.slice(-1) + words[1];
} else {
words.push(tmp.slice(-1));
}
}
if (ctx.measureText(line + words[0]).width < maxWidth) {
line += words.shift() + " ";
} else {
lines.push(line);
line = "";
}
if (words.length === 0) {
lines.push(line);
}
}
Note: In each iteration of the outer loop, the inner check is done first prior to the words being added to the current line.
After the function has processed the sentence into lines, the lines are returned in an array (each row is a new line). Here is a fiddle showing you how to use this code:
Awesome! We're now on our way to getting this on an actual Canvas element.
If you have ever used the Canvas element before you are familiar with having to draw frames, well in this example we will only need to draw on keyup for the inputs.
Here is some basic HTML for the Canvas element and supporting inputs:
<canvas id="canvas" height="200" width="200"></canvas>
<input type="text" id="sentence" value="" placeholder="your text here" />
onkeyup for the input you will want to trigger the draw method.
document.getElementById('sentence').onkeyup = draw;
You may do some other processing in the draw method, however for this demo we will only dump the text to the canvas element (top down).
function draw() {
var lines = fragmentText(this.value, canvas.width * 0.9), // 10% padding
font_size = 22; // px
ctx.font = "Bold " + font_size + "px Arial";
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
lines.forEach(function(line, i) {
ctx.fillText(line, canvas.width / 2, (i + 1) * font_size); // assume font height.
});
ctx.restore();
}
And there you go! Wrapping text on a Canvas element made easy. Here is a complete demo:
Further Readings:
Real World Example:
Hello Robert, I found you at stackoverflow with the same topic question. I commented on you there but haven't replied yet. So I will repeat same question here. :)
ReplyDelete"the code doesn't recognizes 'ENTER' value. And if user enters a long word/characters that beyond the maxWidth allowed, it doesn't produce space for the following word. Any suggestion? BTW, I converted the text input into a textarea."
I'm stuck with this problem for a week so I guess I need a help from you.
Thanks in advance
It shouldn't recognize enter value, it's not built that way.
DeleteHowever if you listened for it you could force a new line I suppose. And you are right! Thanks, I have never noticed this bug before.
I will look into it and update my post,
Thanks for the reply!