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
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);
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.
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/
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/
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);
I created a tiny library for this scenario here: Canvas-Txt
It renders text in multi-line and it offers decent alignment modes.
In order to use this, you will need to either install it or use a CDN.
npm install canvas-txt --save
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 }
/* 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 */
}
<script src="https://unpkg.com/canvas-txt@2.0.6/build/index.js"></script>
<canvas id="myCanvas" width="300" height="160"></canvas>
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.