Auto line-wrapping in SVG text

157,987

Solution 1

Text wrapping is not part of SVG1.1, the currently implemented spec.

In case you are going to use your SVG graphic on the Web, you can embed HTML inside SVG via the <foreignObject/> element. Example:

<svg ...>

<switch>
<foreignObject x="20" y="90" width="150" height="200">
<p xmlns="http://www.w3.org/1999/xhtml">Text goes here</p>
</foreignObject>

<text x="20" y="20">Your SVG viewer cannot display html.</text>
</switch>

</svg>

If you are targeting a pure SVG renderer without HTML support or want your graphic to be editable using professional vector graphics manipulation software (Adobe Illustrator, Inkscape, ...), this solution will probably not suit you.

Solution 2

Here's an alternative:

<svg ...>
  <switch>
    <g requiredFeatures="http://www.w3.org/Graphics/SVG/feature/1.2/#TextFlow">
      <textArea width="200" height="auto">
       Text goes here
      </textArea>
    </g>
    <foreignObject width="200" height="200" 
     requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
      <p xmlns="http://www.w3.org/1999/xhtml">Text goes here</p>
    </foreignObject>
    <text x="20" y="20">No automatic linewrapping.</text>
  </switch>
</svg>

Noting that even though foreignObject may be reported as being supported with that featurestring, there's no guarantee that HTML can be displayed because that's not required by the SVG 1.1 specification. There is no featurestring for html-in-foreignobject support at the moment. However, it is still supported in many browsers, so it's likely to become required in the future, perhaps with a corresponding featurestring.

Note that the 'textArea' element in SVG Tiny 1.2 supports all the standard svg features, e.g advanced filling etc, and that you can specify either of width or height as auto, meaning that the text can flow freely in that direction. ForeignObject acts as clipping viewport.

Note: while the above example is valid SVG 1.1 content, in SVG 2 the 'requiredFeatures' attribute has been removed, which means the 'switch' element will try to render the first 'g' element regardless of having support for SVG 1.2 'textArea' elements. See SVG2 switch element spec.

Solution 3

The textPath may be good for some case.

<svg width="200" height="200"
    xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
 <defs>
  <!-- define lines for text lies on -->
  <path id="path1" d="M10,30 H190 M10,60 H190 M10,90 H190 M10,120 H190"></path>
 </defs>
 <use xlink:href="#path1" x="0" y="35" stroke="blue" stroke-width="1" />
 <text transform="translate(0,35)" fill="red" font-size="20">
  <textPath xlink:href="#path1">This is a long long long text ......</textPath>
 </text>
</svg>

Solution 4

Building on @Mike Gledhill's code, I've taken it a step further and added more parameters. If you have a SVG RECT and want text to wrap inside it, this may be handy:

function wraptorect(textnode, boxObject, padding, linePadding) {

    var x_pos = parseInt(boxObject.getAttribute('x')),
    y_pos = parseInt(boxObject.getAttribute('y')),
    boxwidth = parseInt(boxObject.getAttribute('width')),
    fz = parseInt(window.getComputedStyle(textnode)['font-size']);  // We use this to calculate dy for each TSPAN.

    var line_height = fz + linePadding;

// Clone the original text node to store and display the final wrapping text.

   var wrapping = textnode.cloneNode(false);        // False means any TSPANs in the textnode will be discarded
   wrapping.setAttributeNS(null, 'x', x_pos + padding);
   wrapping.setAttributeNS(null, 'y', y_pos + padding);

// Make a copy of this node and hide it to progressively draw, measure and calculate line breaks.

   var testing = wrapping.cloneNode(false);
   testing.setAttributeNS(null, 'visibility', 'hidden');  // Comment this out to debug

   var testingTSPAN = document.createElementNS(null, 'tspan');
   var testingTEXTNODE = document.createTextNode(textnode.textContent);
   testingTSPAN.appendChild(testingTEXTNODE);

   testing.appendChild(testingTSPAN);
   var tester = document.getElementsByTagName('svg')[0].appendChild(testing);

   var words = textnode.textContent.split(" ");
   var line = line2 = "";
   var linecounter = 0;
   var testwidth;

   for (var n = 0; n < words.length; n++) {

      line2 = line + words[n] + " ";
      testing.textContent = line2;
      testwidth = testing.getBBox().width;

      if ((testwidth + 2*padding) > boxwidth) {

        testingTSPAN = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
        testingTSPAN.setAttributeNS(null, 'x', x_pos + padding);
        testingTSPAN.setAttributeNS(null, 'dy', line_height);

        testingTEXTNODE = document.createTextNode(line);
        testingTSPAN.appendChild(testingTEXTNODE);
        wrapping.appendChild(testingTSPAN);

        line = words[n] + " ";
        linecounter++;
      }
      else {
        line = line2;
      }
    }

    var testingTSPAN = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
    testingTSPAN.setAttributeNS(null, 'x', x_pos + padding);
    testingTSPAN.setAttributeNS(null, 'dy', line_height);

    var testingTEXTNODE = document.createTextNode(line);
    testingTSPAN.appendChild(testingTEXTNODE);

    wrapping.appendChild(testingTSPAN);

    testing.parentNode.removeChild(testing);
    textnode.parentNode.replaceChild(wrapping,textnode);

    return linecounter;
}

document.getElementById('original').onmouseover = function () {

    var container = document.getElementById('destination');
    var numberoflines = wraptorect(this,container,20,1);
    console.log(numberoflines);  // In case you need it

};

Solution 5

This functionality can also be added using JavaScript. Carto.net has an example:

http://old.carto.net/papers/svg/textFlow/

Something else that also might be useful to are you are editable text areas:

http://old.carto.net/papers/svg/gui/textbox/

Share:
157,987
tillda
Author by

tillda

Updated on July 08, 2022

Comments

  • tillda
    tillda almost 2 years

    I would like to display a <text> in SVG what would auto-line-wrap to the container <rect> the same way as HTML text fills <div> elements. Is there a way to do it? I don't want to position lines separately by using <tspan>s.

  • Erik Dahlström
    Erik Dahlström over 13 years
    That is the wrong way to use switch, it needs to use one of the featurestrings defined in the svg spec. The fallback will never be used in your example. See w3.org/TR/SVG11/feature.html and w3.org/TR/SVG11/struct.html#SwitchElement.
  • Rajkamal Subramanian
    Rajkamal Subramanian about 12 years
    I was testing this code in FF, the browser didnt showed me either the textArea element or the foreignObject child. Then after reading the spec, found that requiredFeatures attribute behaves in such a way that, when its list evalutes to false, the element which has the requiredFeatures attribute and its children are not processed. So there wont be any necessity for the switch element. After i removed the switch element, the foreignObject kids were visible (because my browser(FF, 8.01) support svg1.1 ). So i think there is no need of switch element here. Please let me know.
  • Erik Dahlström
    Erik Dahlström about 12 years
    Updated now to use a <g> element. The svg spec didn't tell viewers to look at 'requiredFeatures' on unknown elements, so one has to use a known svg element for it to work as intended.
  • Doug Amos
    Doug Amos almost 11 years
    Also <foreignObject/> is not supported in IE
  • johndodo
    johndodo over 10 years
    Thanks! I needed to use xhtml:div instead of div, but that could be because of d3.js. I couldn't find any useful reference about TextFlow, does it (still) exist or was it just in some draft?
  • Nilloc
    Nilloc over 10 years
    Only in a case where wraping mid word (and not hyphenating) is acceptable. I can't think of many cases beyond art projects where that's ok. http://jsfiddle.net/nilloc/vL3zj/
  • Abhishek
    Abhishek almost 10 years
    It should be noted that textarea seems to not be supported going forward bugzilla.mozilla.org/show_bug.cgi?id=413360
  • akshayb
    akshayb almost 10 years
    thanks. that works perfectly in Chrome. But it doesn't work in firefox. It says on demo link. Unexpected value NaN parsing dy attribute. svgtext_clean2.htm:117 trying to find a work around.
  • hrabinowitz
    hrabinowitz almost 10 years
    But be aware that not all engines can render foreignObjects. In particular, batik does not.
  • MSC
    MSC almost 10 years
    I subsequently got it working in Firefox. Here you go:
  • MSC
    MSC almost 10 years
    (Pressed ENTER too soon just now.) I subsequently got it working in Firefox and IE. If you need some help, have a look at democra.me/wrap_8_may_2014.htm. There is a comment about Firefox in the code.
  • MSC
    MSC almost 10 years
    As you can see, I've expanded the code a lot to shrink the bounding box up or down or truncate with an ellipsis in the right place.
  • massic80
    massic80 over 9 years
    I'd modify a line in MSC's code: boxwidth = parseInt(boxObject.getAttribute('width')), would just accept width in pixel, while boxwidth = parseInt(boxObject.getBBox().width), would accept any type of measure unit
  • Peter
    Peter almost 8 years
    Auto line-wrapping in SVG text :) My javascript code creates lines when the text is to long. It will be nice if i works on all text tags inside SVG. automatic without changing the id="" in javascript. To bad SVG doenst have multi-lines by itself.
  • Matthias
    Matthias almost 7 years
    404 -- Those links are broken
  • posfan12
    posfan12 about 6 years
    Example does not work in Chrome. Have not tested in other browsers.
  • mcv
    mcv over 5 years
    Does not work in Chrome for me either. Using just svg:text in the switch works fine, but it seems Chrome thinks it supports TextFlow but doesn't support svg:textArea.
  • Zang MingJie
    Zang MingJie over 5 years
    @Nilloc Not everybody uses English, this method is totally fine for Chinese, Japanese or Korean.
  • Nilloc
    Nilloc over 5 years
    @ZangMingJie Wrapping for character based (logographic) languages seems like a totally different use case than splitting words. Which is important in all the romantic/latin/cyrillic/arabic (phonographic) languages, which was my point.
  • Erik Dahlström
    Erik Dahlström over 5 years
    Yes, unfortunately the requiredFeatures attribute has been broken by current browsers (it's essentially just ignored). It's possible that using only foreignObject without the switch is the only real option nowadays, though I haven't tested recently to see how well foreignObject is supported across browsers.
  • Krešimir Galić
    Krešimir Galić about 4 years
    Nice solution, but you can align it in center?
  • Zac
    Zac almost 4 years
    Should be accepted answer tbh. The javascript solution is minimal enough and makes sense.
  • Unrelated
    Unrelated about 3 years
    In case anyone else comes through, foreignObjects don't render in Illustrator
  • vitaly
    vitaly about 3 years
    foreign objects are not available in Inkscape or ImageMagick convert. That creates problems trying to use such SVGs in LaTeX.
  • Klesun
    Klesun over 2 years
    Note, the xmlns:xlink="http://www.w3.org/1999/xlink" attribute in the root svg element is vital for that to work.