Automatyczne zawijanie wierszy w tekście SVG

108

Chciałbym wyświetlić <text>w SVG, co spowoduje automatyczne zawijanie linii do kontenera w <rect>taki sam sposób, jak tekst HTML wypełnia <div>elementy. Czy jest na to sposób? Nie chcę umieszczać linii sporadycznie za pomocą <tspan>s.

tillda
źródło

Odpowiedzi:

89

Zawijanie tekstu nie jest częścią SVG1.1, obecnie zaimplementowanej specyfikacji. Powinieneś raczej używać HTML poprzez <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>
Tangui
źródło
5
To jest zły sposób używania przełącznika, musi użyć jednego z ciągów cech zdefiniowanych w specyfikacji svg. W Twoim przykładzie nigdy nie zostanie użyta opcja rezerwowa. Zobacz w3.org/TR/SVG11/feature.html i w3.org/TR/SVG11/struct.html#SwitchElement .
Erik Dahlström
22
Również <ForeignObject /> nie jest obsługiwane w IE
Doug Amos,
3
Należy jednak pamiętać, że nie wszystkie silniki mogą renderować ForeignObjects. W szczególności batik nie.
hrabinowitz
69

Oto alternatywa:

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

Zwracając uwagę, że nawet jeśli ForeignObject może zostać zgłoszone jako obsługiwane przez ten ciąg cech, nie ma gwarancji, że HTML może zostać wyświetlony, ponieważ nie jest to wymagane przez specyfikację SVG 1.1. W tej chwili nie ma łańcucha funkcji do obsługi html-in-Foreignobject. Jednak jest nadal obsługiwany w wielu przeglądarkach, więc prawdopodobnie stanie się wymagany w przyszłości, być może z odpowiednim ciągiem funkcji.

Zwróć uwagę, że element „textArea” w SVG Tiny 1.2 obsługuje wszystkie standardowe funkcje SVG, np. Zaawansowane wypełnianie itp., I że możesz określić szerokość lub wysokość jako auto, co oznacza, że ​​tekst może swobodnie przepływać w tym kierunku. ForeignObject działa jako rzutnia przycinająca.

Uwaga: chociaż powyższy przykład jest prawidłową zawartością SVG 1.1, w SVG 2 atrybut „requiredFeatures” został usunięty, co oznacza, że ​​element „switch” będzie próbował wyrenderować pierwszy element „g” niezależnie od obsługi SVG 1.2 ”textArea ' elementy. Patrz specyfikacja elementu przełączającego SVG2 .

Erik Dahlström
źródło
1
Testowałem ten kod w FF, przeglądarka nie pokazała mi ani elementu textArea, ani dziecka ForeignObject. Następnie po przeczytaniu specyfikacji stwierdziliśmy, że atrybut requiredFeatures zachowuje się w taki sposób, że gdy jego lista zmienia się na false, element, który ma atrybut requiredFeatures i jego elementy potomne, nie są przetwarzane. Więc nie będzie potrzeby stosowania elementu przełączającego. Po usunięciu elementu switch, dzieci ForeignObject były widoczne (ponieważ moja przeglądarka (FF, 8.01) obsługuje svg1.1). Myślę więc, że nie ma tu potrzeby stosowania elementu przełączającego. Proszę daj mi znać.
Rajkamal Subramanian
Zaktualizowano teraz, aby używać elementu <g>. Specyfikacja SVG nie mówi widzom, aby patrzyli na „requiredFeatures” na nieznanych elementach, więc trzeba użyć znanego elementu svg, aby działał zgodnie z przeznaczeniem.
Erik Dahlström
Dzięki! Musiałem użyć xhtml:divzamiast div, ale może to być spowodowane d3.js. Nie mogłem znaleźć żadnej użytecznej informacji o TextFlow, czy istnieje (nadal) czy tylko w jakiejś wersji roboczej?
johndodo
2
Należy zauważyć, że obszar tekstowy wydaje się nie być obsługiwany w przyszłości. Bugzilla.mozilla.org/show_bug.cgi?id=413360
George Mauer
1
Przykład nie działa w przeglądarce Chrome. Nie testowałem w innych przeglądarkach.
posfan12
15

W niektórych przypadkach właściwość textPath może być dobra.

<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
źródło
3
Tylko w przypadku, gdy zawijanie słowa w środku (bez dzielenia wyrazów) jest dopuszczalne. Nie przychodzi mi do głowy wiele przypadków poza projektami artystycznymi, w których to jest w porządku. http://jsfiddle.net/nilloc/vL3zj/
Nilloc
4
@Nilloc Nie wszyscy używają angielskiego, ta metoda jest całkowicie dobra dla języka chińskiego, japońskiego czy koreańskiego.
Zang MingJie,
@ZangMingJie Zawijanie dla języków opartych na znakach (logograficznych) wydaje się być zupełnie innym przypadkiem użycia niż dzielenie słów. Co jest ważne we wszystkich językach romantycznych / łacińskich / cyrylickich / arabskich (fonograficznych), o co mi chodziło.
Nilloc,
11

Opierając się na kodzie @Mike Gledhill, poszedłem o krok dalej i dodałem więcej parametrów. Jeśli masz SVG RECT i chcesz zawinąć w nim tekst, może to być przydatne:

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
źródło
dzięki. który działa doskonale w Chrome. Ale to nie działa w przeglądarce Firefox. Mówi na linku demo. Nieoczekiwana wartość NaN analizuje atrybut dy. svgtext_clean2.htm: 117 próbuję znaleźć obejście.
akshayb
Następnie uruchomiłem go w przeglądarce Firefox. Proszę bardzo:
MSC
1
(Nacisnąłem ENTER za wcześnie.) Następnie uruchomiłem go w Firefoksie i IE. Jeśli potrzebujesz pomocy, zajrzyj na stronę democra.me/wrap_8_may_2014.htm . W kodzie jest komentarz dotyczący Firefoksa.
MSC
Jak widać, znacznie rozszerzyłem kod, aby zmniejszyć obwiednię w górę lub w dół lub obciąć ją wielokropkiem we właściwym miejscu.
MSC
Zmodyfikowałbym linię w kodzie MSC: boxwidth = parseInt(boxObject.getAttribute('width'))zaakceptowałbym po prostu szerokość w pikselach, podczas gdy boxwidth = parseInt(boxObject.getBBox().width)zaakceptowałbym dowolny typ jednostki miary
Massimiliano Caniparoli
7

Poniższy kod działa poprawnie. Uruchom fragment kodu, co robi.

Może da się go wyczyścić lub sprawić, by działał automatycznie ze wszystkimi tagami tekstowymi w 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>

Piotr
źródło
1
Automatyczne zawijanie wierszy w tekście SVG :) Mój kod javascript tworzy linie, gdy tekst jest za długi. Będzie miło, gdybym pracował na wszystkich tagach tekstowych wewnątrz SVG. automatycznie bez zmiany id = "" w javascript. Szkoda, że ​​pliki SVG same mają wiele linii.
Peter,
Fajne rozwiązanie, ale można to wyrównać w środku?
Krešimir Galić
Należy zaakceptować odpowiedź tbh. Rozwiązanie javascript jest wystarczająco minimalne i ma sens.
Zac
4

Opublikowałem następującą instrukcję dodawania fałszywego zawijania słów do elementu „tekst” SVG:

Zawijanie słów SVG - Pokaż korek?

Wystarczy dodać prostą funkcję JavaScript, która podzieli Twój ciąg na krótsze elementy „tspan”. Oto przykład tego, jak to wygląda:

Przykład SVG

Mam nadzieję że to pomoże !

Mike Gledhill
źródło