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

5

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?

Grizelda answered 29/6, 2010 at 15:7 Comment(0)
T
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));
Triatomic answered 30/6, 2010 at 21:14 Comment(1)
I have written a library that essentially does just this, named raphael-paragraph. See my answer below.Occam
W
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.

Wallraff answered 27/3, 2012 at 23:56 Comment(2)
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.Cabinetwork
+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.Attila
L
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("")});
};
Leishaleishmania answered 22/3, 2012 at 0:13 Comment(1)
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.Attila
C
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() );
Cal answered 11/9, 2013 at 16:6 Comment(0)
O
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.

Occam answered 24/5, 2015 at 22:54 Comment(2)
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?Offshoot
@Offshoot Either copy the dist/paragraph.js file into your project or npm install raphael-paragraph and import rp from 'raphael-paragraph/src/paragraph'.Occam

© 2022 - 2024 — McMap. All rights reserved.