170

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.

1
  • 1
    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.
    – cubuspl42
    Commented Nov 23, 2021 at 14:15

11 Answers 11

142

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.

6
  • 6
    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. Commented Feb 14, 2011 at 13:43
  • 29
    Also <foreignObject/> is not supported in IE
    – Doug Amos
    Commented Jul 17, 2013 at 12:36
  • 3
    But be aware that not all engines can render foreignObjects. In particular, batik does not. Commented Sep 18, 2014 at 19:33
  • In case anyone else comes through, foreignObjects don't render in Illustrator
    – Unrelated
    Commented Jun 17, 2021 at 7:01
  • 1
    foreign objects are not available in Inkscape or ImageMagick convert. That creates problems trying to use such SVGs in LaTeX.
    – vitaly
    Commented Jun 18, 2021 at 20:30
78

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.

7
  • 1
    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. Commented Apr 5, 2012 at 13:43
  • 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. Commented Apr 5, 2012 at 14:55
  • 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?
    – johndodo
    Commented Dec 11, 2013 at 12:00
  • 2
    It should be noted that textarea seems to not be supported going forward bugzilla.mozilla.org/show_bug.cgi?id=413360 Commented Sep 15, 2014 at 15:28
  • 1
    Example does not work in Chrome. Have not tested in other browsers.
    – posfan12
    Commented May 24, 2018 at 12:00
24

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
  • 6
    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
    Commented Mar 19, 2014 at 5:56
  • 11
    @Nilloc Not everybody uses English, this method is totally fine for Chinese, Japanese or Korean. Commented Dec 11, 2018 at 19:23
  • @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.
    – Nilloc
    Commented Dec 18, 2018 at 17:35
  • 1
    Note, the xmlns:xlink="http://www.w3.org/1999/xlink" attribute in the root svg element is vital for that to work.
    – Klesun
    Commented Feb 18, 2022 at 7:22
13

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>

3
  • 2
    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.
    – Peter
    Commented Jul 4, 2016 at 5:54
  • Nice solution, but you can align it in center? Commented May 11, 2020 at 9:46
  • Should be accepted answer tbh. The javascript solution is minimal enough and makes sense.
    – Zac
    Commented Aug 6, 2020 at 23:39
12

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

};
5
  • 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.
    – akshayb
    Commented Sep 18, 2014 at 11:31
  • I subsequently got it working in Firefox. Here you go:
    – MSC
    Commented Sep 19, 2014 at 22:35
  • 1
    (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
    Commented Sep 19, 2014 at 22:48
  • 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.
    – MSC
    Commented Sep 19, 2014 at 22:49
  • 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
    – massic80
    Commented Feb 24, 2015 at 17:16
9

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/

1
  • 404 -- Those links are broken
    – Matthias
    Commented Aug 3, 2017 at 22:11
4

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 SVG

Hope this helps !

2

Embedded HTML in SVG: <foreignObject>

As pointed out by Erik Dahlström <foreignObject> is currently the easiest way to achieve a multiline text layout in SVG.
Beseides, if you need responsive/dynamic line-wrapping in SVG text – it is currently your only option. <foreignObject> – as the name implies is actually just an embedded HTML (or other XML based) content.

Not a problem for web-only applications since browser support is solid (but you might still encounter issues in more complex contexts such as applying svg filters to foreignObjects masks etc.). However, most desktop graphic editors like inkscape, Adobe Illustrator or preview apps can't render or convert <foreignObject> contents.

Rebuild from HTML elements

This approach takes advantage of native browser APIs to get word or character positions by querying all text nodes in an HTML element.

Disclaimer: the following approaches have limitations due to the limited text capablities of SVG: it only supports some basic block and inline text elements but doesn't support e.g. lists or tables.

Example: convert HTML to native SVG text via <span> wrapping

function foreignObjectToNativeSvgtext(foreignObject) {

  let body = foreignObject.children[0];
  let children = [...body.children];

  // children - will become svg <text> elements
  children.forEach((child, i) => {
    // clone text el
    let type = child.nodeName.toLowerCase();
    let clone;

    clone = document.createElement(type);
    body.insertBefore(clone, body.children[i]);

    let textNodes = textNodesInEl(child);
    let textL = textNodes.length;


    for (let n = 0; n < textL; n++) {
      let textNode = textNodes[n];

      // get computed styles for parent
      let style = window.getComputedStyle(textNode.parentNode);
      copyStyleProps(child, clone)

      // split to words
      let words = textNode.textContent.split(' ');

      words.forEach((word, w) => {
        let newtextNode = document.createTextNode(word);
        let span = document.createElement('span');
        span.classList.add('span2tspan', 'span2tspan' + type);

        // get computed styles for child
        copyStyleProps(textNode.parentNode, span)
        span.textContent = word + ' ';
        clone.appendChild(span);

      })

    }

    // convert to svg tspan
    let nativeText = htmlTextEl2Svg(clone);

    //insert before foreignObject 
    foreignObject.parentNode.insertBefore(nativeText, foreignObject);

    // delete original foreign object elements
    child.remove();

  })

  //preserve whitespace
  let svg = foreignObject.closest('svg');
  svg.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve')
  foreignObject.remove();
  output.value = new XMLSerializer().serializeToString(svg)
}

function htmlTextEl2Svg(el) {
  const ns = "http://www.w3.org/2000/svg";
  let parentSVG = el.closest('svg');
  let newText = document.createElementNS(ns, 'text');
  let parentProps = copyStyleProps(el, newText);
  let bb = el.getBoundingClientRect();
  let {
    x,
    y
  } = bb;
  let point = parentSVG.createSVGPoint();
  point.x = x;
  point.y = y;
  let ctm = parentSVG.getScreenCTM().inverse();
  point = point.matrixTransform(ctm);

  // round
  [x, y] = [x, y].map(val => {
    return +(val).toFixed(3)
  })
  newText.setAttribute('x', x);
  newText.setAttribute('y', y);

  // children
  let children = el.querySelectorAll('.span2tspan');


  /**
   *  add x and y only for vertical shifts
   * (new line breaks)
   */
  let xPrev = 0;
  let yPrev = 0;
  let prevStyle = '';

  children.forEach(child => {

    let bb = child.getBoundingClientRect();
    let {
      x,
      y,
      width,
      height
    } = bb;
    let tspan = document.createElementNS(ns, 'tspan');
    let style = window.getComputedStyle(child);
    let currentProps = copyStyleProps(child, tspan);

    // convert coordinates to svg 
    let point = parentSVG.createSVGPoint();
    point.x = x;
    point.y = y;
    let ctm = parentSVG.getScreenCTM().inverse();
    point = point.matrixTransform(ctm);
    x = point.x;
    // add fontsize to baseline shift
    y = point.y + (parseFloat(style.fontSize) / 1.15);
    // round
    [x, y] = [x, y].map(val => {
      return +(val).toFixed(3)
    });

    if (x !== xPrev && y !== yPrev) {
      tspan.setAttribute('x', x)
    }
    if (y !== yPrev) {
      tspan.setAttribute('y', y)
    }

    // text color to fill
    if (currentProps.color !== 'rgb(0, 0, 0)') {
      tspan.style.fill = currentProps.color;
    }

    // remove superfluous inherited props
    tspan.style.removeProperty('margin');
    tspan.style.removeProperty('padding');

    if (parentProps.fontFamily == currentProps.fontFamily) {
      tspan.style.removeProperty('font-family')
    }
    if (parentProps.fontSize == currentProps.fontSize) {
      tspan.style.removeProperty('font-size')
    }

    if (parentProps.fontWeight == currentProps.fontWeight) {
      tspan.style.removeProperty('font-weight')
    }

    if (parentProps.lineHeight == currentProps.lineHeight) {
      tspan.style.removeProperty('line-height')
    }


    // copy content
    tspan.textContent = child.textContent;

    // stringify current style
    let currentStyle = JSON.stringify(currentProps);

    // add to svg
    if (child.textContent.trim()) {
      let prevTspans = newText.querySelectorAll('tspan');
      let prev = prevTspans[prevTspans.length - 1];

      if (prev && !tspan.getAttribute('x') &&
        !tspan.getAttribute('y') &&
        currentStyle == prevStyle
      ) {
        prev.textContent += tspan.textContent;
      } else {
        newText.appendChild(tspan)
        prevStyle = currentStyle;
      }
    }

    xPrev = x;
    yPrev = y;

  })
  return newText;

}

/**
 * helper copy computed styles
 */
function copyStyleProps(el, target, styleProps = []) {
  let defaultvaluesToExclude = {
    'color': 'rgb(0, 0, 0)',
    'fontStyle': 'normal',
    'letterSpacing': 'normal',
    'verticalAlign': 'baseline',
  }

  let currentProps = {};

  // defaults
  if (!styleProps.length) {
    styleProps = [
      'fontFamily',
      'fontSize',
      'fontStyle',
      'fontWeight',
      'lineHeight',
      'letterSpacing',
      'verticalAlign',
      'margin',
      'padding',
      'color'
    ];
  }
  let style = window.getComputedStyle(el);
  for (prop in style) {
    let val = style[prop];
    if (styleProps.includes(prop) && val !== defaultvaluesToExclude[prop]) {
      target.style[prop] = val;
      currentProps[prop] = val;
    }
  }
  return currentProps;
}



/**
 * Get text nodes in element
 * based on:
 * https://stackoverflow.com/questions/10730309/find-all-text-nodes-in-html-page#10730777
 */
function textNodesInEl(el) {
  let textNodes = [];
  for (el = el.firstChild; el; el = el.nextSibling) {
    if (el.nodeType == 3) {
      textNodes.push(el);
    } else {
      textNodes = textNodes.concat(textNodesInEl(el));
    }
  }
  // filter empty text nodes
  textNodes = textNodes.filter((node) => node.textContent.trim());
  return textNodes;
}
textarea {
  width: 100%;
  min-height: 30em;
  white-space: pre;
  font-family: monospace;
  font-size: 0.75em;
}
<p><button onclick="foreignObjectToNativeSvgtext(foreignObject)">Convert foreignObject</button></p>


<h3>SVG with foreignObject – text is editable</h3>

<svg id="svg" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
        <style>
            .foreignBody {
                font-family: Georgia, serif;
                font-size: 1em;
                line-height: 1.5em;
            }

            h1 {
                font-family: sans-serif;
                font-size: 2em;
                line-height: 1.2em;
                margin: 0 0 1rem 0;
            }

            .author {
                line-height: 1.2em;
                font-style: italic;
                margin-bottom: 0em;
            }

            p {
                margin: 0 0 1rem 0;
            }

            sup {
                line-height: 0px;
                font-size: 0.5em;
            }

            ul li:marker{
                content:'•';
                color:red;
            }


        </style>
        <foreignObject id="foreignObject" x="15" y="5" width="90%" height="90%">
            <div class="foreignBody" xmlns="http://www.w3.org/1999/xhtml" contenteditable>
                <p class="author">Franz Kafka</p>
                <h1>The Metamorphosis</h1>
                <p>One morning, when <strong>Gregor Samsa</strong> woke from troubled dreams, he found himself
                    transformed in
                    his bed into <em style="color:red; letter-spacing:0.1em">a horrible</em> vermin.<sup>1</sup></em>
                </p>

                <p>He lay on his armour-like back, and if he lifted his head a little he could see his brown belly,
                    slightly
                    domed and divided by arches <strong><em> into stiff sections.</em></strong> The bedding was hardly
                    able to
                    cover it and seemed ready to slide off any moment.</p>

            </div>
        </foreignObject>

    </svg>

<h3>Output</h3>
<textarea id="output"></textarea>

How it works

  1. loop through all block elements like <p> ,<h1>

  2. split text content into separate text nodes

  3. retrieve style info for each text node via getComputedStyle()

  4. get positions via getBoundingClientRect()

  5. convert HTML coordinates to svg user units:

         let point = parentSVG.createSVGPoint();
         point.x = x;
         point.y = y;
         let ctm = parentSVG.getScreenCTM().inverse();
         point = point.matrixTransform(ctm);  
    
  6. replace text nodes with <text>and <tspan> elements

Future SVG2 concepts (not yet supported!)

  1. The inline-size property

  2. Whitespace sensitive rendering. Firefox renders line wraps if whitespace:pre is applied to <text> – on the other hand this won't work for optimized/minified svgs (when excessive white space gets removed)

Firefox: svg line wrap

text{
    white-space: pre;
    word-break: break-word;
}

svg{
  width:20em;
  border: 1px solid #ccc;  
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100" xml:space="preserve">

 <text x="0" y="20"  font-size="10">The Metamorphosis
One morning, when 
Gregor Samsa woke 
from troubled dreams, 
he found himself transformed
in his bed into a 
horrible vermin.1
 </text>
</svg>

Related posts

3
  • 1
    Upvoted because you used Kafka for your example text.
    – Raydot
    Commented Nov 29, 2023 at 19:53
  • 1
    I guess Kafka wasn't aware of the term "debugging" while writing this novel =) Since we may never see a proper SVG multiline text composing concept (although it should have been implemented from day one), a dummy text conveying a desperate situation seems quite appropriate. Hopefully I need to update this post. BTW you might have a look at this neat dummy text generator and this variable font tool called "Samsa" – not affiliated with these projects! Commented Nov 30, 2023 at 0:35
  • 1
    Best comment ever.
    – Raydot
    Commented Dec 1, 2023 at 0:43
1

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

  }

another example with dynamic JS

   const myTextContent = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's s";
   const lineLength = 20;
   const maxLineHeight = 15;
   const mySVG = document.querySelector("svg");
   let loopMax = Math.round(myTextContent.length/lineLength);
   const txts = [];
   for (let i=1; i<=loopMax; i++){
      txts.push(myTextContent.substring((lineLength*i)-lineLength,lineLength*i));
   }
   txts.forEach( (txt,i)=>{
     const newTxt = document.createElementNS("http://www.w3.org/2000/svg", "text");
     newTxt.setAttribute("x", "0");
     newTxt.setAttribute("y", `${maxLineHeight * (i+1)}`);
     newTxt.setAttribute("fill", "red");
     newTxt.textContent = txt;
     mySVG.appendChild(newTxt);
   });
<svg height="90" width="200"> 
</svg>

2
  • Same here, none of them works for me.
    – Will
    Commented Aug 18, 2022 at 3:08
  • I work with the text itself just split it to new elements depend on the required length but note the height of the shape must allow all the new texts and also this way will not listen for word-break rule so it may break the word at any place I will edit the answer and add example with js dynamic Commented Aug 18, 2022 at 13:14
0
const svg=document.getElementsByTagName('svg')[0]


const type=(str,x=100,y=100,width=100,height=100,lineHeight=10)=>{
  const g=(el='g',parent=svg,attrs={},html='')=>{
    const child=document.createElementNS('http://www.w3.org/2000/svg',el)
    parent.appendChild(child)
    Object.keys(attrs).forEach(a=>child.setAttribute(a,attrs[a]))
    child.innerHTML=html
    return child
  }

  const rows=[]
  for(let i=0;i<=Math.floor(height/lineHeight);i++){
    rows.push(`M${x},${y+i*lineHeight} L${x+width},${y+i*lineHeight}`)
  }
  
  const id='text'+svg.children.length
  const text=g('text',svg)
  const underPath=g('path',text,{d:rows.join('\n'),stroke:'red',id})
  const tp=g('textPath',text,{href:'#'+id},str)
  
  //calc 1-try get 1 row, 2-try add enough spaces
  let p=str.split(' ').reverse().map(a=>a+' ')
  let k=[]
  tp.innerHTML='.'
  let currentRow=tp.getBoundingClientRect().height
  do{
        k=k.concat(p.reverse())
        p=[]
        tp.innerHTML=k.join('')
        while(tp.getBoundingClientRect().height>currentRow){
          p.push(k.pop())
          tp.innerHTML=k.join('')
        }

      k[k.length-1]=('&nbsp;').repeat(120)+k[k.length-1].replace(' ','')
      tp.innerHTML=k.join('')
      while((tp.getBoundingClientRect().height>currentRow) && k[k.length-1].match('&nbsp;')){
        k[k.length-1]=k[k.length-1].replace('&nbsp;','')
        tp.innerHTML=k.join('')
      }
    tp.innerHTML=k.join('')+'..'
    currentRow=tp.getBoundingClientRect().height
  }while(p.length>1)
    
  tp.innerHTML=k.concat(p.reverse()).join('')
  
}


type('Quick brown fox jumps over the lazy dog. Quick brown fox jumps over the lazy dog.',10,100,170,50)

https://codepen.io/D-K-the-lessful/pen/yLZrpOE?editors=0011 word wrapping on textPath and algorithm reduce fillers and checking size of element enter image description here

1
  • Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation? Commented Dec 21, 2023 at 0:21
-1

2023 Edition for Web (tested in all major browsers):

<switch>
  <foreignObject requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
    <p id="ModernText">My really long text</p>
  </foreignObject>

  <text id="FallbackText">Fallback Description</text>
</switch>

Not the answer you're looking for? Browse other questions tagged or ask your own question.