I am currently trying to add some text, which is added dynamically, using RaphaelJS. I want the text to break at 200 pixels, then continue on the next line. Just like if you had a regular DIV with a width in HTML.
https://jsfiddle.net/qLvuztcy/
I've tried something like:
.text {
width: 200px;
max-width: 200px; //and this
}
But that's not how it works, unfortunately. Does anyone know how I can achieve this? I know that adding a \n to the text will make a new line, but I'm not sure how to achieve what I want.
I found an answer for Snap.svg which I've modified a bit. It works perfectly, however.
function text(x, y, txt, max_width, element_class) {
var abc = "abcdefghijklmnopqrstuvwxyzæøåABCDEFGHIJKLMNOPQRSTUVWXYZÆØÅ";
var temp = rsr.text(50, 50, abc);
temp.node.classList.add("text");
var letter_width = temp.getBBox().width / abc.length;
temp.remove();
var heights = [];
var loopLines = txt.split("\n");
for (j = 0; j < loopLines.length; j++) {
var words = loopLines[j].split(" ");
var width_so_far = 0,
current_line = 0,
lines = [''];
for (var i = 0; i < words.length; i++) {
var l = words[i].length;
if (width_so_far + (l * letter_width) > max_width) {
lines.push('');
current_line++;
width_so_far = 0;
}
width_so_far += l * letter_width;
lines[current_line] += words[i] + " ";
}
if (j > 0) {
y += heights[j - 1];
}
lines = lines.join("\n");
var t = rsr.text(x, y, lines);
$(t.node).find("tspan").attr("dy", "1.2em");
t.node.classList.add(element_class);
heights.push(t.getBBox().height);
}
}
Requires jQuery, as I am doing the find("tspan") with jQuery (might as well, since I use it other places as well).
I've modified it to now support \n in your strings as well.
SVG doesn't have automatic text wrapping. Well not in the current version anyway.
You have to split the text up into lines yourself. Or if you are displaying the SVG in a browser, you have the option of embedding HTML in the SVG. You can do this with the <foreignObject> element.
I'm not sure whether Raphael has an API for creating foreignObject elements. You'll need to investigate.
I am trying to add text on an image using the <canvas> element. First the image is drawn and on the image the text is drawn. So far so good.
But where I am facing a problem is that if the text is too long, it gets cut off in the start and end by the canvas. I don't plan to resize the canvas, but I was wondering how to wrap the long text into multiple lines so that all of it gets displayed. Can anyone point me at the right direction?
Updated version of #mizar's answer, with one severe and one minor bug fixed.
function getLines(ctx, text, maxWidth) {
var words = text.split(" ");
var lines = [];
var currentLine = words[0];
for (var i = 1; i < words.length; i++) {
var word = words[i];
var width = ctx.measureText(currentLine + " " + word).width;
if (width < maxWidth) {
currentLine += " " + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
return lines;
}
We've been using this code for some time, but today we were trying to figure out why some text wasn't drawing, and we found a bug!
It turns out that if you give a single word (without any spaces) to the getLines() function, it will return an empty array, rather than an array with a single line.
While we were investigating that, we found another (much more subtle) bug, where lines can end up slightly longer than they should be, since the original code didn't account for spaces when measuring the length of a line.
Our updated version, which works for everything we've thrown at it, is above. Let me know if you find any bugs!
A possible method (not completely tested, but as for now it worked perfectly)
/**
* Divide an entire phrase in an array of phrases, all with the max pixel length given.
* The words are initially separated by the space char.
* #param phrase
* #param length
* #return
*/
function getLines(ctx,phrase,maxPxLength,textStyle) {
var wa=phrase.split(" "),
phraseArray=[],
lastPhrase=wa[0],
measure=0,
splitChar=" ";
if (wa.length <= 1) {
return wa
}
ctx.font = textStyle;
for (var i=1;i<wa.length;i++) {
var w=wa[i];
measure=ctx.measureText(lastPhrase+splitChar+w).width;
if (measure<maxPxLength) {
lastPhrase+=(splitChar+w);
} else {
phraseArray.push(lastPhrase);
lastPhrase=w;
}
if (i===wa.length-1) {
phraseArray.push(lastPhrase);
break;
}
}
return phraseArray;
}
Here was my spin on it... I read #mizar's answer and made some alterations to it... and with a little assistance I Was able to get this.
code removed, see fiddle.
Here is example usage. http://jsfiddle.net/9PvMU/1/ - this script can also be seen here and ended up being what I used in the end... this function assumes ctx is available in the parent scope... if not you can always pass it in.
edit
the post was old and had my version of the function that I was still tinkering with. This version seems to have met my needs thus far and I hope it can help anyone else.
edit
It was brought to my attention there was a small bug in this code. It took me some time to get around to fixing it but here it is updated. I have tested it myself and it seems to work as expected now.
function fragmentText(text, maxWidth) {
var words = text.split(' '),
lines = [],
line = "";
if (ctx.measureText(text).width < maxWidth) {
return [text];
}
while (words.length > 0) {
var split = false;
while (ctx.measureText(words[0]).width >= maxWidth) {
var tmp = words[0];
words[0] = tmp.slice(0, -1);
if (!split) {
split = true;
words.splice(1, 0, tmp.slice(-1));
} else {
words[1] = tmp.slice(-1) + words[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;
}
context.measureText(text).width is what you're looking for...
Try this script to wrap the text on a canvas.
<script>
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '';
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}
var canvas = document.getElementById('Canvas01');
var ctx = canvas.getContext('2d');
var maxWidth = 400;
var lineHeight = 24;
var x = (canvas.width - maxWidth) / 2;
var y = 70;
var text = 'HTML is the language for describing the structure of Web pages. HTML stands for HyperText Markup Language. Web pages consist of markup tags and plain text. HTML is written in the form of HTML elements consisting of tags enclosed in angle brackets (like <html>). HTML tags most commonly come in pairs like <h1> and </h1>, although some tags represent empty elements and so are unpaired, for example <img>..';
ctx.font = '15pt Calibri';
ctx.fillStyle = '#555555';
wrapText(ctx, text, x, y, maxWidth, lineHeight);
</script>
</body>
See demo here http://codetutorial.com/examples-canvas/canvas-examples-text-wrap.
From the script here: http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/
I've extended to include paragraph support. Use \n for new line.
function wrapText(context, text, x, y, line_width, line_height)
{
var line = '';
var paragraphs = text.split('\n');
for (var i = 0; i < paragraphs.length; i++)
{
var words = paragraphs[i].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 > line_width && n > 0)
{
context.fillText(line, x, y);
line = words[n] + ' ';
y += line_height;
}
else
{
line = testLine;
}
}
context.fillText(line, x, y);
y += line_height;
line = '';
}
}
Text can be formatted like so:
var text =
[
"Paragraph 1.",
"\n\n",
"Paragraph 2."
].join("");
Use:
wrapText(context, text, x, y, line_width, line_height);
in place of
context.fillText(text, x, y);
I am posting my own version used here since answers here weren't sufficient for me. The first word needed to be measured in my case, to be able to deny too long words from small canvas areas. And I needed support for 'break+space, 'space+break' or double-break/paragraph-break combos.
wrapLines: function(ctx, text, maxWidth) {
var lines = [],
words = text.replace(/\n\n/g,' ` ').replace(/(\n\s|\s\n)/g,'\r')
.replace(/\s\s/g,' ').replace('`',' ').replace(/(\r|\n)/g,' '+' ').split(' '),
space = ctx.measureText(' ').width,
width = 0,
line = '',
word = '',
len = words.length,
w = 0,
i;
for (i = 0; i < len; i++) {
word = words[i];
w = word ? ctx.measureText(word).width : 0;
if (w) {
width = width + space + w;
}
if (w > maxWidth) {
return [];
} else if (w && width < maxWidth) {
line += (i ? ' ' : '') + word;
} else {
!i || lines.push(line !== '' ? line.trim() : '');
line = word;
width = w;
}
}
if (len !== i || line !== '') {
lines.push(line);
}
return lines;
}
It supports any variants of lines breaks, or paragraph breaks, removes double spaces, as well as leading or trailing paragraph breaks. It returns either an empty array if the text doesn't fit. Or an array of lines ready to draw.
look at https://developer.mozilla.org/en/Drawing_text_using_a_canvas#measureText%28%29
If you can see the selected text, and see its wider than your canvas, you can remove words, until the text is short enough. With the removed words, you can start at the second line and do the same.
Of course, this will not be very efficient, so you can improve it by not removing one word, but multiple words if you see the text is much wider than the canvas width.
I did not research, but maybe their are even javascript libraries that do this for you
I modified it using the code from here http://miteshmaheta.blogspot.sg/2012/07/html5-wrap-text-in-canvas.html
http://jsfiddle.net/wizztjh/kDy2U/41/
This should bring the lines correctly from the textbox:-
function fragmentText(text, maxWidth) {
var lines = text.split("\n");
var fittingLines = [];
for (var i = 0; i < lines.length; i++) {
if (canvasContext.measureText(lines[i]).width <= maxWidth) {
fittingLines.push(lines[i]);
}
else {
var tmp = lines[i];
while (canvasContext.measureText(tmp).width > maxWidth) {
tmp = tmp.slice(0, tmp.length - 1);
}
if (tmp.length >= 1) {
var regex = new RegExp(".{1," + tmp.length + "}", "g");
var thisLineSplitted = lines[i].match(regex);
for (var j = 0; j < thisLineSplitted.length; j++) {
fittingLines.push(thisLineSplitted[j]);
}
}
}
}
return fittingLines;
And then get draw the fetched lines on the canvas :-
var lines = fragmentText(textBoxText, (rect.w - 10)); //rect.w = canvas width, rect.h = canvas height
for (var showLines = 0; showLines < lines.length; showLines++) { // do not show lines that go beyond the height
if ((showLines * resultFont.height) >= (rect.h - 10)) { // of the canvas
break;
}
}
for (var i = 1; i <= showLines; i++) {
canvasContext.fillText(lines[i-1], rect.clientX +5 , rect.clientY + 10 + (i * (resultFont.height))); // resultfont = get the font height using some sort of calculation
}
This is a typescript version of #JBelfort's answer.
(By the way, thanks for this brilliant code)
As he mentioned in his answer this code can simulate html element such as textarea,and also the CSS property
word-break: break-all
I added canvas location parameters (x, y and lineHeight)
function wrapText(
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number,
x: number,
y: number,
lineHeight: number
) {
const xOffset = x;
let yOffset = y;
const lines = text.split('\n');
const fittingLines: [string, number, number][] = [];
for (let i = 0; i < lines.length; i++) {
if (ctx.measureText(lines[i]).width <= maxWidth) {
fittingLines.push([lines[i], xOffset, yOffset]);
yOffset += lineHeight;
} else {
let tmp = lines[i];
while (ctx.measureText(tmp).width > maxWidth) {
tmp = tmp.slice(0, tmp.length - 1);
}
if (tmp.length >= 1) {
const regex = new RegExp(`.{1,${tmp.length}}`, 'g');
const thisLineSplitted = lines[i].match(regex);
for (let j = 0; j < thisLineSplitted!.length; j++) {
fittingLines.push([thisLineSplitted![j], xOffset, yOffset]);
yOffset += lineHeight;
}
}
}
}
return fittingLines;
}
and you can just use this like
const wrappedText = wrapText(ctx, dialog, 200, 100, 200, 50);
wrappedText.forEach(function (text) {
ctx.fillText(...text);
});
}
I'm writing a program where entered text is compared to an original (for memorization help).
Whenever a letter is wrong, I want the letter to turn red. However, when I loop it to set the textField for the wrong letters:
function checkAgainstBible(inputText:String):void
{
var outputTextL:String = ""
for(var n:Number = 0; n < inputText.length; n++)
{
var inputTextL:String = inputText
var specLetter:String = inputTextL.charAt(n);
if(inputText.charAt(n) != bibleVerse.charAt(n))
{
outputTextL = outputTextL + specLetter
outputText.text = outputTextL;
outputText.setTextFormat(red, n, n+1);
}
else
{
outputTextL = outputTextL + specLetter
outputText.text = outputTextL;
outputText.setTextFormat(green, n, n+1);
}
}
It overwrites the old one, making it so only the last letter is formatted. How do I avoid this?
EDIT I could use HTML text, but I would like to re-insert spaces afterwards, and I couldn't do that with the extra text HTML text adds to the string specLetter. /EDIT
You can set the whole text first, then set the textformat
function checkAgainstBible(inputText:String):void
{
outputText.text = inputText;
for(var n:Number = 0; n < inputText.length; n++)
{
var inputTextL:String = inputText;
var specLetter:String = inputTextL.charAt(n);
if(inputText.charAt(n) != bibleVerse.charAt(n))
{
outputText.setTextFormat(red, n, n+1);
}
else
{
outputText.setTextFormat(green, n, n+1);
}
}
}
I have a problem in ActionScript 3 with a project where I am to output a bunch of data in a diagram and as pure text. This is not the problem, though. The problem is that when the user changes the type of data he wants shown. I would then remove the currently shown columns in the diagram, and add new ones. But my code removes ALL columns, so that there is no diagram at all!
I've done it all in Flash CS5, and the columns are instances of an object I made (a rectangle) which I just add to the stage. All the instances are added to the container "container." When I want to remove them, and add new ones I use:
var container = new Sprite(); // The container for my diagram
function emptyContainer() {
if (container.numChildren > 0) {
container.removeChildAt(0);
}
}
Which works.
I have switch statement that adds the different columns based on user-input. The name of the object (the rectangle) is "soyle." The name of the instances is "stolpe"
(I am norwegian by the way, so that's the deal with those weird words)
function fChange(event:Event) {
textToBeReturned = "";
switch (treCbox.selectedItem.label) {
case "Furu":
for (var a = 1; a < table.length; a++ ){
textToBeReturned += "I år: " + table[a][0] + " - " + table[a][1] + " millioner trær." + "\n";
// Diagram
var stolpe = new soyle;
stolpe.width = table[a][1];
stolpe.x = 20;
stolpe.y = 100 + (a * 30);
container.addChild(stolpe);
// Description for the columns
var textbox = new TextField;
textbox.text = "År " + table[a][0];
textbox.x = stolpe.width + 30;
textbox.y = stolpe.y;
container.addChild(textbox);
}
break;
case "Gran":
for (var b = 1; b < table.length; b++ ){
textToBeReturned += "I år: " + table[b][0] + " - " + table[b][2] + " millioner trær." + "\n" ;
var stolpe = new soyle;
stolpe.width = table[b][1];
stolpe.x = 20;
stolpe.y = 100 + (b * 30);
container.addChild(stolpe);
// Description for the columns
var textbox = new TextField;
textbox.text = "År " + table[b][0];
textbox.x = stolpe.width + 30;
textbox.y = stolpe.y;
container.addChild(textbox);
}
break;
case "Lauvtre":
for (var c = 1; c < table.length; c++ ){
textToBeReturned += "I år: " + table[c][0] + " - " + table[c][3] + " millioner trær." + "\n" ;
var stolpe = new soyle;
stolpe.width = table[c][1];
stolpe.x = 20;
stolpe.y = 100 + (c * 30);
container.addChild(stolpe);
// Description for the columns
var textbox = new TextField;
textbox.text = "År " + table[c][0];
textbox.x = stolpe.width + 30;
textbox.y = stolpe.y;
container.addChild(textbox);
}
break;
}
txtReturn.text = textToBeReturned;
};
I wonder where to place my function for removing all children, to remove just those children that were added earlier. To matter where I place the function (at the start of fChange, at the beginning of each case) there is NO diagram shown, either new or old.
There is of course an array with the data, but I do not post it as it is irrelevant.
Thank you for your time, and in advance for your help! :)
Couple of problems here. I assume you want to remove all children from your container MC before adding new children inside your switch. (Tip for next time, try to post only the code your working with. The switch statement here is large and overwhelming. It's also not really relevant)
Your function emptyContainer does not actually remove all children, it only removes one at index 0. This needs to be inside of a loop.
function emptyContainer() {
while (container.numChildren > 0) { //changed the if to a while. This will run until there are no children left in container
container.removeChildAt(0);
}
}
You're going to want to call this first thing inside of your fChanged function
function fChange(event:Event) {
emptyContainer()
/* rest of function */
}
try this out and let me know how it works.