Automatischer Zeilenumbruch in SVG-Text

108

Ich möchte <text>in SVG anzeigen, was automatisch in Zeilen um den Container gewickelt wird, <rect>genauso wie HTML-Text <div>Elemente füllt . Gibt es eine Möglichkeit, dies zu tun? Ich möchte Linien nicht sparsam mit <tspan>s positionieren .

tillda
quelle

Antworten:

89

Textumbruch ist nicht Teil von SVG1.1, der derzeit implementierten Spezifikation. Sie sollten lieber HTML über das <foreignObject/>Element verwenden.

<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>
Tangui
quelle
5
Dies ist die falsche Verwendung von switch. Es muss eine der in der svg-Spezifikation definierten Funktionen verwendet werden. Der Fallback wird in Ihrem Beispiel niemals verwendet. Siehe w3.org/TR/SVG11/feature.html und w3.org/TR/SVG11/struct.html#SwitchElement .
Erik Dahlström
22
Auch <ForeignObject /> wird im IE nicht unterstützt
Doug Amos
3
Beachten Sie jedoch, dass nicht alle Engines fremde Objekte rendern können. Insbesondere Batik nicht.
Hrabinowitz
69

Hier ist eine 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>

Es wird darauf hingewiesen, dass, obwohl ForeignObject möglicherweise von diesem Feature unterstützt wird, keine Garantie dafür besteht, dass HTML angezeigt werden kann, da dies in der SVG 1.1-Spezifikation nicht erforderlich ist. Derzeit gibt es keine Möglichkeit für die Unterstützung von HTML-in-Foreign-Objekten. Es wird jedoch in vielen Browsern weiterhin unterstützt, sodass es wahrscheinlich in Zukunft erforderlich sein wird, möglicherweise mit einem entsprechenden Merkmal.

Beachten Sie, dass das 'textArea'-Element in SVG Tiny 1.2 alle Standard-SVG-Funktionen unterstützt, z. B. erweiterte Füllung usw., und dass Sie entweder Breite oder Höhe als automatisch angeben können, was bedeutet, dass der Text frei in diese Richtung fließen kann. ForeignObject fungiert als Clipping-Ansichtsfenster.

Hinweis: Während das obige Beispiel gültiger SVG 1.1-Inhalt ist, wurde in SVG 2 das Attribut 'requiredFeatures' entfernt. Dies bedeutet, dass das Element 'switch' versucht, das erste Element 'g' zu rendern, unabhängig davon, ob SVG 1.2 'textArea unterstützt wird 'Elemente. Siehe SVG2-Schalterelementspezifikation .

Erik Dahlström
quelle
1
Ich habe diesen Code in FF getestet. Der Browser hat mir weder das textArea-Element noch das untergeordnete Element objectObject angezeigt. Nachdem Sie die Spezifikation gelesen haben, haben Sie festgestellt, dass sich das Attribut requireFeatures so verhält, dass das Element mit dem Attribut requiredFeatures und seine untergeordneten Elemente nicht verarbeitet werden, wenn die Liste auf false ausgewertet wird. Das Schalterelement ist also nicht erforderlich. Nachdem ich das Switch-Element entfernt hatte, waren die ForeignObject-Kids sichtbar (weil mein Browser (FF, 8.01) svg1.1 unterstützt). Ich denke also, dass hier kein Schalterelement erforderlich ist. Lass es mich wissen, bitte.
Rajkamal Subramanian
Jetzt aktualisiert, um ein <g> -Element zu verwenden. Die svg-Spezifikation hat den Zuschauern nicht gesagt, dass sie sich 'requiredFeatures' für unbekannte Elemente ansehen sollen, daher muss ein bekanntes svg-Element verwendet werden, damit es wie beabsichtigt funktioniert.
Erik Dahlström
Vielen Dank! Ich musste xhtml:divstattdessen verwenden div, aber das könnte an d3.js liegen. Ich konnte keine nützliche Referenz zu TextFlow finden. Existiert es (noch) oder war es nur in einem Entwurf?
Johndodo
2
Es sollte beachtet werden, dass Textarea in Zukunft nicht unterstützt zu werden scheint bugzilla.mozilla.org/show_bug.cgi?id=413360
George Mauer
1
Beispiel funktioniert nicht in Chrome. Habe nicht in anderen Browsern getestet.
posfan12
15

Der textPath kann in einigen Fällen gut sein.

<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>
user2856765
quelle
3
Nur in einem Fall, in dem das Umschließen eines mittleren Wortes (und nicht das Trennen) akzeptabel ist. Ich kann mir nicht viele Fälle jenseits von Kunstprojekten vorstellen, in denen das in Ordnung ist. http://jsfiddle.net/nilloc/vL3zj/
Nilloc
4
@Nilloc Nicht jeder spricht Englisch, diese Methode ist für Chinesisch, Japanisch oder Koreanisch völlig in Ordnung.
Zang MingJie
@ZangMingJie Das Umschließen von zeichenbasierten (logografischen) Sprachen scheint ein völlig anderer Anwendungsfall zu sein als das Teilen von Wörtern. Was in allen romantischen / lateinischen / kyrillischen / arabischen (phonografischen) Sprachen wichtig ist, war mein Punkt.
Nilloc
11

Aufbauend auf dem Code von @Mike Gledhill bin ich noch einen Schritt weiter gegangen und habe weitere Parameter hinzugefügt. Wenn Sie ein SVG RECT haben und möchten, dass Text darin eingeschlossen wird, kann dies nützlich sein:

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

};
MSC
quelle
Vielen Dank. das funktioniert perfekt in Chrome. Aber es funktioniert nicht in Firefox. Es heißt auf Demo-Link. Unerwarteter Wert NaN Parsing Dy Attribut. svgtext_clean2.htm: 117 versucht, eine Lösung zu finden.
Akshayb
Ich habe es später in Firefox zum Laufen gebracht. Los geht's:
MSC
1
(Drücken Sie gerade jetzt zu früh die EINGABETASTE.) Anschließend funktionierte es in Firefox und IE. Wenn Sie Hilfe benötigen, schauen Sie sich democra.me/wrap_8_may_2014.htm an . Der Code enthält einen Kommentar zu Firefox.
MSC
Wie Sie sehen können, habe ich den Code stark erweitert, um den Begrenzungsrahmen nach oben oder unten zu verkleinern oder mit einem Auslassungspunkt an der richtigen Stelle abzuschneiden.
MSC
Ich würde eine Zeile im MSC-Code ändern: Ich boxwidth = parseInt(boxObject.getAttribute('width'))würde nur die Breite in Pixel akzeptieren, während ich boxwidth = parseInt(boxObject.getBBox().width)jede Art von Maßeinheit akzeptieren würde
Massimiliano Caniparoli
7

Der folgende Code funktioniert einwandfrei. Führen Sie das Code-Snippet aus, was es tut.

Möglicherweise kann es bereinigt werden oder automatisch mit allen Text-Tags in SVG funktionieren.

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>

Peter
quelle
1
Automatischer Zeilenumbruch in SVG-Text :) Mein Javascript-Code erstellt Zeilen, wenn der Text zu lang ist. Es wird schön sein, wenn ich an allen Text-Tags in SVG arbeite. automatisch, ohne die id = "" in Javascript zu ändern. Schade, dass SVG nicht mehrere Zeilen für sich hat.
Peter
Schöne Lösung, aber Sie können es in der Mitte ausrichten?
Krešimir Galić
Sollte akzeptiert werden Antwort tbh. Die Javascript-Lösung ist minimal genug und sinnvoll.
Zac
4

Ich habe die folgende exemplarische Vorgehensweise zum Hinzufügen eines falschen Zeilenumbruchs zu einem SVG-Textelement hier veröffentlicht:

SVG Word Wrap - Stopper anzeigen?

Sie müssen nur eine einfache JavaScript-Funktion hinzufügen, die Ihre Zeichenfolge in kürzere "tspan" -Elemente aufteilt. Hier ist ein Beispiel dafür, wie es aussieht:

Beispiel SVG

Hoffe das hilft !

Mike Gledhill
quelle