Ajuste de línea automático en texto SVG

108

Me gustaría mostrar <text>en SVG lo que se ajustaría automáticamente al contenedor de <rect>la misma manera que el texto HTML llena los <div>elementos. ¿Hay una manera de hacerlo? No quiero colocar las líneas con moderación usando <tspan>s.

tillda
fuente

Respuestas:

89

El ajuste de texto no es parte de SVG1.1, la especificación actualmente implementada. Preferiría usar HTML a través del <foreignObject/>elemento.

<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
fuente
5
Esa es la forma incorrecta de usar switch, necesita usar una de las cadenas de características definidas en la especificación svg. La alternativa nunca se utilizará en su ejemplo. Consulte w3.org/TR/SVG11/feature.html y w3.org/TR/SVG11/struct.html#SwitchElement .
Erik Dahlström
22
Además, <ForeignObject /> no es compatible con IE
Doug Amos
3
Pero tenga en cuenta que no todos los motores pueden representar objetos externos. En particular, el batik no lo hace.
hrabinowitz
69

Aquí tienes una alternativa:

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

Teniendo en cuenta que, aunque se puede informar que ForeignObject es compatible con esa cadena de características, no hay garantía de que se pueda mostrar HTML porque no es requerido por la especificación SVG 1.1. Por el momento, no hay una cadena de características para el soporte de objetos html-in-foreigno. Sin embargo, todavía es compatible con muchos navegadores, por lo que es probable que sea necesario en el futuro, quizás con una cadena de características correspondiente.

Tenga en cuenta que el elemento 'textArea' en SVG Tiny 1.2 admite todas las características estándar de svg, por ejemplo, relleno avanzado, etc., y que puede especificar el ancho o el alto como automático, lo que significa que el texto puede fluir libremente en esa dirección. ForeignObject actúa como ventana gráfica de recorte.

Nota: si bien el ejemplo anterior es contenido SVG 1.1 válido, en SVG 2 se ha eliminado el atributo 'requiredFeatures', lo que significa que el elemento 'switch' intentará representar el primer elemento 'g' independientemente de que sea compatible con SVG 1.2 'textArea 'elementos. Consulte las especificaciones del elemento conmutador SVG2 .

Erik Dahlström
fuente
1
Estaba probando este código en FF, el navegador no me mostró el elemento textArea ni el elemento secundario ForeignObject. Luego, después de leer la especificación, descubrió que el atributo requiredFeatures se comporta de tal manera que, cuando su lista se evalúa como falsa, el elemento que tiene el atributo requiredFeatures y sus hijos no se procesan. Por lo tanto, no será necesario el elemento interruptor. Después de eliminar el elemento switch, los niños de ForeignObject eran visibles (porque mi navegador (FF, 8.01) admite svg1.1). Entonces creo que no hay necesidad de un elemento interruptor aquí. Por favor hagamelo saber.
Rajkamal Subramanian
Actualizado ahora para usar un elemento <g>. La especificación svg no le dijo a los espectadores que miraran 'requiredFeatures' en elementos desconocidos, por lo que uno tiene que usar un elemento svg conocido para que funcione según lo previsto.
Erik Dahlström
¡Gracias! Necesitaba usar en xhtml:divlugar de div, pero eso podría deberse a d3.js. No pude encontrar ninguna referencia útil sobre TextFlow, ¿existe (todavía) o solo estaba en algún borrador?
johndodo
2
Cabe señalar que textarea parece no ser compatible en el futuro bugzilla.mozilla.org/show_bug.cgi?id=413360
George Mauer
1
El ejemplo no funciona en Chrome. No lo he probado en otros navegadores.
posfan12
15

TextPath puede ser bueno para algunos casos.

<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>
usuario2856765
fuente
3
Solo en un caso en el que sea aceptable envolver la mitad de la palabra (y no separar con guiones). No puedo pensar en muchos casos más allá de los proyectos de arte en los que eso está bien. http://jsfiddle.net/nilloc/vL3zj/
Nilloc
4
@Nilloc No todo el mundo usa el inglés, este método está bien para chino, japonés o coreano.
Zang MingJie
@ZangMingJie El envolver para lenguajes basados ​​en caracteres (logográficos) parece un caso de uso totalmente diferente al de dividir palabras. Lo cual es importante en todos los idiomas románticos / latinos / cirílicos / árabes (fonográficos), que era mi punto.
Nilloc
11

Basándome en el código de @Mike Gledhill, he dado un paso más y he añadido más parámetros. Si tiene un SVG RECT y desea que el texto se ajuste dentro de él, esto puede ser útil:

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
fuente
Gracias. que funciona perfectamente en Chrome. Pero no funciona en Firefox. Dice en el enlace de demostración. Valor inesperado atributo dy de análisis de NaN. svgtext_clean2.htm: 117 tratando de encontrar una solución alternativa.
akshayb
Posteriormente lo hice funcionar en Firefox. Aquí tienes:
MSC
1
(Presioné ENTER demasiado pronto en este momento). Posteriormente lo hice funcionar en Firefox e IE. Si necesita ayuda, eche un vistazo a democr.me/wrap_8_may_2014.htm . Hay un comentario sobre Firefox en el código.
MSC
Como puede ver, he expandido mucho el código para encoger el cuadro delimitador hacia arriba o hacia abajo o truncarlo con puntos suspensivos en el lugar correcto.
MSC
Modificaría una línea en el código de MSC:, boxwidth = parseInt(boxObject.getAttribute('width'))solo aceptaría ancho en píxeles, mientras queboxwidth = parseInt(boxObject.getBBox().width) , aceptaría cualquier tipo de unidad de medida
Massimiliano Caniparoli
7

El siguiente código está funcionando bien. Ejecute el fragmento de código que hace.

Tal vez se pueda limpiar o hacer que funcione automáticamente con todas las etiquetas de texto en 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>

Pedro
fuente
1
Ajuste automático de líneas en texto SVG :) Mi código javascript crea líneas cuando el texto es demasiado largo. Sería bueno si trabajo en todas las etiquetas de texto dentro de SVG. automático sin cambiar el id = "" en javascript. Lástima que SVG tenga varias líneas por sí mismo.
Peter
Buena solución, pero ¿puedes alinearla en el centro?
Krešimir Galić
Debería aceptarse la respuesta tbh. La solución de JavaScript es lo suficientemente mínima y tiene sentido.
Zac
4

He publicado el siguiente tutorial para agregar algunos ajustes de palabras falsos a un elemento de "texto" SVG aquí:

Envoltorio de palabras SVG - ¿Mostrar tapón?

Solo necesita agregar una función JavaScript simple, que divide su cadena en elementos "tspan" más cortos. A continuación, se muestra un ejemplo de cómo se ve:

Ejemplo de SVG

Espero que esto ayude !

Mike Gledhill
fuente