-->

Auto line-wrapping in SVG text

2019-01-01 00:54发布

问题:

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 sparately by using <tspan>s.

回答1:

Text wrapping is not part of SVG1.1, the currently implemented spec. You should rather use HTML via the <foreignObject/> element.

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


回答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.



回答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>


回答4:

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/



回答5:

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

};


回答6:

If you were to use d3.js, this could help: https://bl.ocks.org/mbostock/7555321



回答7:

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:

\"Example

Hope this helps !



回答8:

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 = \'<tspan id=\"PROCESSING\">busy</tspan >\';

  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 += \'<tspan x=\"0\" dy=\"\' + y + \'\">\' + line + \'</tspan>\';
      line = words[n] + \' \';
    } else {
      line = testLine;
    }
  }
  
  element.innerHTML += \'<tspan x=\"0\" dy=\"\' + y + \'\">\' + line + \'</tspan>\';
  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;
}
<svg height=\"300\" width=\"500\" xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\">

  <text id=\"test\" y=\"0\">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.</text>

</svg>