Quebra automática de linha em texto SVG

108

Eu gostaria de exibir um <text>em SVG que iria quebrar a linha automaticamente no contêiner <rect>da mesma maneira que o texto HTML preenche os <div>elementos. Existe uma forma de fazer isso? Não quero posicionar as linhas esparsamente usando <tspan>s.

Tillda
fonte

Respostas:

89

A quebra automática de texto não faz parte do SVG1.1, a especificação atualmente implementada. Você deve preferir usar HTML por meio do <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
fonte
5
Essa é a maneira errada de usar o switch, ele precisa usar uma das sequências de recursos definidas na especificação do svg. O fallback nunca será usado em seu exemplo. Consulte w3.org/TR/SVG11/feature.html e w3.org/TR/SVG11/struct.html#SwitchElement .
Erik Dahlström
22
Além disso, <ForeignObject /> não é compatível com o IE
Doug Amos
3
Mas esteja ciente de que nem todos os mecanismos podem renderizar objetos estrangeiros. Em particular, o batik não.
hrabinowitz de
69

Aqui está uma 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>

Observando que, embora o ForeignObject possa ser relatado como sendo compatível com essa sequência de recursos, não há garantia de que o HTML possa ser exibido porque isso não é exigido pela especificação SVG 1.1. Não há sequência de recursos para suporte a objetos html-em-estrangeiro no momento. No entanto, ele ainda é compatível com muitos navegadores, portanto, provavelmente será necessário no futuro, talvez com uma sequência de recursos correspondente.

Observe que o elemento 'textArea' no SVG Tiny 1.2 oferece suporte a todos os recursos svg padrão, por exemplo, preenchimento avançado, etc., e que você pode especificar a largura ou a altura como automática, o que significa que o texto pode fluir livremente nessa direção. ForeignObject atua como janela de exibição de recorte.

Observação: embora o exemplo acima seja um conteúdo SVG 1.1 válido, no SVG 2 o atributo 'requiredFeatures' foi removido, o que significa que o elemento 'switch' tentará renderizar o primeiro elemento 'g' independentemente de ter suporte para SVG 1.2 'textArea 'elementos. Consulte as especificações do elemento switch SVG2 .

Erik Dahlström
fonte
1
Eu estava testando este código no FF, o navegador não me mostrou o elemento textArea ou o filho ForeignObject. Então, depois de ler a especificação, descobrimos que o atributo requiredFeatures se comporta de tal forma que, quando sua lista é avaliada como false, o elemento que possui o atributo requiredFeatures e seus filhos não são processados. Portanto, não haverá necessidade do elemento switch. Depois de remover o elemento switch, as crianças estrangeiras ficaram visíveis (porque meu navegador (FF, 8.01) suporta svg1.1). Então eu acho que não há necessidade de mudar o elemento aqui. Por favor deixe-me saber.
Rajkamal Subramanian,
Atualizado agora para usar um elemento <g>. A especificação svg não diz aos visualizadores para olharem 'requiredFeatures' em elementos desconhecidos, então é necessário usar um elemento svg conhecido para que funcione conforme o esperado.
Erik Dahlström,
Obrigado! Eu precisei usar em xhtml:divvez de div, mas isso pode ser por causa de d3.js. Não consegui encontrar nenhuma referência útil sobre TextFlow, ele (ainda) existe ou estava apenas em algum rascunho?
johndodo
2
Deve-se observar que textarea parece não ser suportado daqui para frente bugzilla.mozilla.org/show_bug.cgi?id=413360
George Mauer
1
O exemplo não funciona no Chrome. Não testei em outros navegadores.
posfan12
15

O textPath pode ser bom para alguns 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>
user2856765
fonte
3
Somente no caso em que a quebra de linha do meio (e não a hifenização) é aceitável. Não consigo pensar em muitos casos além de projetos de arte em que isso seja ok. http://jsfiddle.net/nilloc/vL3zj/
Nilloc
4
@Nilloc Nem todo mundo usa inglês, este método é totalmente adequado para chinês, japonês ou coreano.
Zang MingJie
@ZangMingJie Wrapping para linguagens baseadas em caracteres (logográficas) parece um caso de uso totalmente diferente do que separar palavras. O que é importante em todas as línguas românticas / latinas / cirílicas / árabes (fonográficas), esse foi o meu ponto.
Nilloc
11

Com base no código de @Mike Gledhill, dei um passo adiante e adicionei mais parâmetros. Se você tiver um SVG RECT e quiser que o texto seja quebrado dentro dele, isso pode 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
fonte
obrigado. que funciona perfeitamente no Chrome. Mas não funciona no firefox. Diz no link de demonstração. Valor inesperado NaN analisando o atributo dy. svgtext_clean2.htm: 117 tentando encontrar uma solução alternativa.
akshayb
Posteriormente, comecei a trabalhar no Firefox. Aqui está:
MSC
1
(Pressionei ENTER cedo demais.) Posteriormente, comecei a trabalhar no Firefox e no IE. Se precisar de ajuda, dê uma olhada em democra.me/wrap_8_may_2014.htm . Há um comentário sobre o Firefox no código.
MSC
Como você pode ver, expandi muito o código para reduzir a caixa delimitadora para cima ou para baixo ou truncar com reticências no lugar certo.
MSC
Eu modificaria uma linha no código do MSC boxwidth = parseInt(boxObject.getAttribute('width'))boxwidth = parseInt(boxObject.getBBox().width)
:,
7

O código a seguir está funcionando bem. Execute o trecho de código o que ele faz.

Talvez ele possa ser limpo ou fazê-lo funcionar automaticamente com todas as marcas de texto em 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>

Peter
fonte
1
Quebra automática de linha em texto SVG :) Meu código javascript cria linhas quando o texto é muito longo. Seria bom se eu trabalhasse em todas as tags de texto dentro do SVG. automático sem alterar o id = "" em javascript. Para piorar, o SVG não tem várias linhas por si só.
Peter
Boa solução, mas você pode alinhá-la no centro?
Krešimir Galić
Deve ser aceita resposta tbh. A solução javascript é mínima o suficiente e faz sentido.
Zac
4

Publiquei o seguinte passo a passo para adicionar alguma quebra de linha falsa a um elemento de "texto" SVG aqui:

SVG Word Wrap - Mostrar limitador?

Você só precisa adicionar uma função JavaScript simples, que divide sua string em elementos "tspan" mais curtos. Aqui está um exemplo de sua aparência:

SVG de exemplo

Espero que isto ajude !

Mike Gledhill
fonte