29

I'm creating an SVG text box using the Raphael library, and filling it with a dynamic string which is extracted from an XML document.

Sometimes, this string is longer than the canvas I am placing the text box on, so I need to either limit the width of the box which will itself force the line breaks (I can't find any evidence of this being possible) OR ensure that a '\n' line break is inserted after a certain amount of characters.

So (1) is this the best option? And (2) how would I do this?

Jack Roscoe
  • 4,293
  • 10
  • 37
  • 46

5 Answers5

47

There isn't an attribute for text wrapping, but there is a simple trick you can use. Add one word at a time to a text object and when it gets too wide, add a line feed. You can use the getBBox() function to determine the width. Basically, you emulate an old fashioned typewriter. Here is a sample of some code that will do this for you. You could easily turn this into a simple function that takes the text and a width.

var r = Raphael(500, 500);
var t = r.text(100, 100).attr('text-anchor', 'start');
var maxWidth = 100;

var content = "Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate. ";
var words = content.split(" ");

var tempText = "";
for (var i=0; i<words.length; i++) {
  t.attr("text", tempText + " " + words[i]);
  if (t.getBBox().width > maxWidth) {
    tempText += "\n" + words[i];
  } else {
    tempText += " " + words[i];
  }
}

t.attr("text", tempText.substring(1));
Mark
  • 5,499
  • 34
  • 29
9

thanks for the answer. However, I found that I needed a few adjustments to work for me:

function textWrap(t, width) {
    var content = t.attr("text");
    var abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    t.attr({
      'text-anchor' : 'start',
      "text" : abc
    });
    var letterWidth = t.getBBox().width / abc.length;
    t.attr({
        "text" : content
    });

    var words = content.split(" ");
    var x = 0, s = [];
    for ( var i = 0; i < words.length; i++) {

        var l = words[i].length;
        if (x + (l * letterWidth) > width) {
            s.push("\n");
            x = 0;
        }
        x += l * letterWidth;
        s.push(words[i] + " ");
    }
    t.attr({
        "text" : s.join("")
    });
}

The changes were:

  • the comparison needed to use (l * letterwidth) ... not just l
  • the if/else changed to just an if - so that a line break will always set X to 0
  • and always add the new l * letterwidth to the x value

hope this helps.

Evan
  • 101
  • 1
  • 5
  • 1
    Thanks for this - although I'm not sure why you need this code at the top: `'text-anchor': 'start'`. I removed this and it seemed to work ok and preserve my original formatting. – rwalter Aug 02 '12 at 11:10
  • 1
    +1 This is an improvement on Cancerbero's code and the best option on the page if performance is a factor. One possible improvement: letterwidth is constant per font and font-size, so if you're running this over large amounts of text, you can get a modest performance boost by storing letterwidth in an appropriately scoped variable, instead of re-calculating it with the text attr switching for each element. I'd recommend storing it in an object, keyed by font-family and font-size. – user56reinstatemonica8 Dec 17 '12 at 10:58
3

mark's solution is slow for large amounts of text (firefox 11). I think that is because the text is re rendered several times for getting BBOX. the following function is more efficient for large amounts of text but perhaps less exact (code from raphaelmarkup project):

/**
 * @param t a raphael text shape
 * @param width - pixels to wrapp text width
 * modify t text adding new lines characters for wrapping it to given width.
 */
rm._textWrapp = function(t, width) {
    var content = t.attr("text");
    var abc="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    t.attr({'text-anchor': 'start', "text": abc});
    var letterWidth=t.getBBox().width / abc.length;
    t.attr({"text": content});
    var words = content.split(" "), x=0, s=[];
    for ( var i = 0; i < words.length; i++) {
        var l = words[i].length;
        if(x+l>width) {
            s.push("\n")
            x=0;
        }
        else {
            x+=l*letterWidth;
        }
        s.push(words[i]+" ");
    }
    t.attr({"text": s.join("")});
};
cancerbero
  • 6,799
  • 1
  • 32
  • 24
  • Nice, but `if(x+l>width) {` looks like a mistake - it treats the average letter width of the last word in each test as 1px. Works much better if you lose the `else` clause and put `x+=l*letterWidth;` at the start of each `for` loop. Also `'text-anchor': 'start',` is unnecessary - no need to force left alignment. **edit** just saw that Evan's answer takes these into account and is in response to this code, not Mark's. – user56reinstatemonica8 Dec 17 '12 at 10:52
0

Well, i solved it tweaking it a little bit

var words = server.split( " " );
var length = words.length;
var temp_text = "";

for( var i = 0; i < length; i++ ) {
    temp_text = temp_text + ' ' + words[i];
    t.attr( "text", temp_text );

    if( t.getBBox().width > width ) {
        temp_text = temp_text.replace(/( *)(\w+)$/, "\n$2");
    }
}

t.attr( "text", temp_text.trim() );
Carlos
  • 192
  • 1
  • 1
  • 8
0

I know it's a little belated now, but you might be interested in my Raphael-paragraph project which does this automatically.

Raphael-paragraph allows you to create auto-wrapped multiline text with maximum width and height constraints, line height and text style configuration. It can hyphenate long words and truncate them if they exceed vertical bounds. It's still quite beta-ish and requires a lot of optimization, but it should work for your purposes.

Usage examples and documentation are provided on the GitHub page.

Jimmy Breck-McKye
  • 2,923
  • 1
  • 22
  • 32
  • Can you provide details on how to use your library? Which file to import in script tag and which method to call for text wrap? – Masu Mar 18 '20 at 11:42
  • @Masu Either copy the `dist/paragraph.js` file into your project or `npm install raphael-paragraph` and `import rp from 'raphael-paragraph/src/paragraph'`. – Jimmy Breck-McKye Mar 18 '20 at 14:53