Auto line-wrapping in SVG text
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:
tillda
Updated on July 08, 2022Comments
-
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 over 13 yearsThat 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 about 12 yearsI 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 about 12 yearsUpdated 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 almost 11 yearsAlso <foreignObject/> is not supported in IE
-
johndodo over 10 yearsThanks! I needed to use
xhtml:div
instead ofdiv
, 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 over 10 yearsOnly 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 almost 10 yearsIt should be noted that textarea seems to not be supported going forward bugzilla.mozilla.org/show_bug.cgi?id=413360
-
akshayb almost 10 yearsthanks. 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 almost 10 yearsBut be aware that not all engines can render foreignObjects. In particular, batik does not.
-
MSC almost 10 yearsI subsequently got it working in Firefox. Here you go:
-
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 almost 10 yearsAs 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 over 9 yearsI'd modify a line in MSC's code:
boxwidth = parseInt(boxObject.getAttribute('width'))
, would just accept width in pixel, whileboxwidth = parseInt(boxObject.getBBox().width)
, would accept any type of measure unit -
Peter almost 8 yearsAuto 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 almost 7 years404 -- Those links are broken
-
posfan12 about 6 yearsExample does not work in Chrome. Have not tested in other browsers.
-
mcv over 5 yearsDoes 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 over 5 years@Nilloc Not everybody uses English, this method is totally fine for Chinese, Japanese or Korean.
-
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 over 5 yearsYes, 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ć about 4 yearsNice solution, but you can align it in center?
-
Zac almost 4 yearsShould be accepted answer tbh. The javascript solution is minimal enough and makes sense.
-
Unrelated about 3 yearsIn case anyone else comes through, foreignObjects don't render in Illustrator
-
vitaly about 3 yearsforeign objects are not available in Inkscape or ImageMagick convert. That creates problems trying to use such SVGs in LaTeX.
-
Klesun over 2 yearsNote, the
xmlns:xlink="http://www.w3.org/1999/xlink"
attribute in the root svg element is vital for that to work.