How to either determine SVG text box width, or force line breaks after 'x' characters?

20,241

Solution 1

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));

Solution 2

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.

Solution 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("")});
};
Share:
20,241
Jack Roscoe
Author by

Jack Roscoe

Updated on May 25, 2020

Comments

  • Jack Roscoe
    Jack Roscoe almost 4 years

    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?