ChatGPT解决这个技术问题 Extra ChatGPT

Auto line-wrapping in SVG text

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.

While this might sound philosophical, probably the reason why text wrapping wasn't originally included in the SVG standard is because it was meant to be a language for describing graphics, not content. The support for any text is already "stretching" that definition, possibly because of accessibility (screen readers) and usability (text selection) reasons, and possibly more. But still, with that definition in mind, you (or your image editor!) should probably lay out the text in the way you consider final and aesthetically pleasing.

c
cubuspl42

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.


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.
Also is not supported in IE
But be aware that not all engines can render foreignObjects. In particular, batik does not.
In case anyone else comes through, foreignObjects don't render in Illustrator
foreign objects are not available in Inkscape or ImageMagick convert. That creates problems trying to use such SVGs in LaTeX.
E
Erik Dahlström

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.


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.
Updated now to use a 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.
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?
It should be noted that textarea seems to not be supported going forward bugzilla.mozilla.org/show_bug.cgi?id=413360
Example does not work in Chrome. Have not tested in other browsers.
u
user2856765

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>

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/
@Nilloc Not everybody uses English, this method is totally fine for Chinese, Japanese or Korean.
@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.
Note, the xmlns:xlink="http://www.w3.org/1999/xlink" attribute in the root svg element is vital for that to work.
M
MSC

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

};

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.
I subsequently got it working in Firefox. Here you go:
(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.
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.
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
P
Peter

The following code is working fine. Run the code snippet what it does.

Maybe it can be cleaned up or make it automatically work with all text tags in SVG.

function svg_textMultiline() { var x = 0; var y = 20; var width = 360; var lineHeight = 10; /* get the text */ var element = document.getElementById('test'); var text = element.innerHTML; /* split the words into array */ var words = text.split(' '); var line = ''; /* Make a tspan for testing */ element.innerHTML = 'busy'; for (var n = 0; n < words.length; n++) { var testLine = line + words[n] + ' '; var testElem = document.getElementById('PROCESSING'); /* Add line in testElement */ testElem.innerHTML = testLine; /* Messure textElement */ var metrics = testElem.getBoundingClientRect(); testWidth = metrics.width; if (testWidth > width && n > 0) { element.innerHTML += '' + line + ''; line = words[n] + ' '; } else { line = testLine; } } element.innerHTML += '' + line + ''; document.getElementById("PROCESSING").remove(); } svg_textMultiline(); body { font-family: arial; font-size: 20px; } svg { background: #dfdfdf; border:1px solid #aaa; } svg text { fill: blue; stroke: red; stroke-width: 0.3; stroke-linejoin: round; stroke-linecap: round; } GIETEN - Het college van Aa en Hunze is in de fout gegaan met het weigeren van een zorgproject in het failliete hotel Braams in Gieten. Dat stelt de PvdA-fractie in een brief aan het college. De partij wil opheldering over de kwestie en heeft schriftelijke vragen ingediend. Verkeerde route De PvdA vindt dat de gemeenteraad eerst gepolst had moeten worden, voordat het college het plan afwees. "Volgens ons is de verkeerde route gekozen", zegt PvdA-raadslid Henk Santes.


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.
Nice solution, but you can align it in center?
Should be accepted answer tbh. The javascript solution is minimal enough and makes sense.
j
jbeard4

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/


404 -- Those links are broken
C
Community

I have posted the following walkthrough for adding some fake word-wrapping to an SVG "text" element here:

SVG Word Wrap - Show stopper?

You just need to add a simple JavaScript function, which splits your string into shorter "tspan" elements. Here's an example of what it looks like:

https://i.stack.imgur.com/Bpvui.png

Hope this helps !


M
Mahmoud Magdy

I tried all answers None of them works with me only I created noob solution but it will solve without unknown lines of code, Try to add additional text tag without content and validate the text length if it > than maximum first text length add the rest to another text tag and so on. you just need Simple JavaScript if statement and change the text Content

  if (data['clinic']['cicovidcliniccity'].length > 35 && data['clinic']['cicovidcliniccity'].length < 75) {
    const cname = data['clinic']['cicovidcliniccity'];
    const ctext2_shodow = document.querySelector("#c_text2_shdow");
    ctext2.textContent = cname.substring(1, 35)
    ctext2_shodow.textContent = cname.substring(35, cname.length);

  }

  if (data['clinic']['cicovidcliniccity'].length > 75 && data['clinic']['cicovidcliniccity'].length < 110) {
    const cname1 = data['clinic']['cicovidcliniccity'];
    const ctext2_shodow = document.querySelector("#c_text2_shdow");
    const ctext3_shodow = document.querySelector("#c_text3_shdow");
    ctext2.textContent = cname1.substring(1, 35)
    ctext2_shodow.textContent = cname1.substring(35, 75);
    ctext3_shodow.textContent = cname1.substring(75, cname1.length);

  }