Автоматический перенос строк в тексте SVG

108

Я хотел бы отобразить <text>в SVG то, что будет автоматически переноситься строкой в ​​контейнер так <rect>же, как текст HTML заполняет <div>элементы. Как это сделать? Я не хочу размещать строки равномерно, используя <tspan>s.

Тилда
источник

Ответы:

89

Перенос текста не является частью SVG1.1, в настоящее время реализованной спецификации. Лучше использовать HTML через <foreignObject/>элемент.

<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>
Танги
источник
5
Это неправильный способ использования переключателя, он должен использовать одну из строк функций, определенных в спецификации svg. Резервный вариант никогда не будет использоваться в вашем примере. См. W3.org/TR/SVG11/feature.html и w3.org/TR/SVG11/struct.html#SwitchElement .
Эрик Дальстрем,
22
Также <foreignObject /> не поддерживается в IE
Дуг Амос
3
Но имейте в виду, что не все движки могут отображать foreignObject. В частности, батика нет.
hrabinowitz
69

Вот альтернатива:

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

Отмечая, что даже если сообщается, что foreignObject поддерживается этой строкой функций, нет никакой гарантии, что HTML может отображаться, потому что это не требуется спецификацией SVG 1.1. На данный момент нет строки функций для поддержки html-in-foreignobject. Тем не менее, он по-прежнему поддерживается во многих браузерах, поэтому, вероятно, он станет необходим в будущем, возможно, с соответствующей строкой функций.

Обратите внимание, что элемент textArea в SVG Tiny 1.2 поддерживает все стандартные функции svg, например расширенное заполнение и т.д., и что вы можете указать ширину или высоту как auto, что означает, что текст может свободно перемещаться в этом направлении. ForeignObject действует как область просмотра отсечения.

Примечание: хотя приведенный выше пример является допустимым содержимым SVG 1.1, в SVG 2 атрибут 'requiredFeatures' был удален, что означает, что элемент 'switch' будет пытаться отобразить первый элемент 'g' независимо от наличия поддержки SVG 1.2 'textArea элементы. См. Спецификацию переключающего элемента SVG2 .

Эрик Дальстрём
источник
1
Я тестировал этот код в FF, браузер не показывал мне ни элемент textArea, ни дочерний элемент foreignObject. Затем, после прочтения спецификации, обнаружил, что атрибут requiredFeatures ведет себя таким образом, что, когда его список принимает значение false, элемент, имеющий атрибут requiredFeatures, и его дочерние элементы не обрабатываются. Таким образом, в элементе переключения не будет необходимости. После того, как я удалил элемент переключателя, дети foreignObject были видны (потому что мой браузер (FF, 8.01) поддерживает svg1.1). Поэтому я думаю, что здесь нет необходимости в переключающем элементе. Пожалуйста, дайте мне знать.
Rajkamal Subramanian
Обновлено для использования элемента <g>. Спецификация svg не говорила зрителям смотреть на «requiredFeatures» для неизвестных элементов, поэтому нужно использовать известный элемент svg, чтобы он работал должным образом.
Эрик Дальстрем
Спасибо! Мне нужно было использовать xhtml:divвместо div, но это могло быть из-за d3.js. Я не нашел полезной ссылки на TextFlow, он (все еще) существует или только в черновике?
johndodo,
2
Следует отметить, что textarea, похоже, не будет поддерживаться в будущем bugzilla.mozilla.org/show_bug.cgi?id=413360
Джордж Мауэр
1
Пример не работает в Chrome. Не тестировал в других браузерах.
posfan12
15

В некоторых случаях textPath может подойти.

<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
источник
3
Только в том случае, если допустимо перенос текста в середину слова (а не перенос через дефис). Я не могу вспомнить много случаев, помимо арт-проектов, где это нормально. http://jsfiddle.net/nilloc/vL3zj/
Ниллок,
4
@Nilloc Не все используют английский, этот метод полностью подходит для китайского, японского или корейского языков.
Zang MingJie
@ZangMingJie Обертывание для символьных (логографических) языков кажется совершенно другим вариантом использования, чем разделение слов. Что важно для всех романтических / латинских / кириллических / арабских (фонографических) языков, о чем я говорил.
Ниллок
11

Основываясь на коде @Mike Gledhill, я пошел дальше и добавил больше параметров. Если у вас есть SVG RECT и вы хотите, чтобы текст переносился внутрь него, это может быть удобно:

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

};
МСК
источник
Спасибо. который отлично работает в Chrome. Но в firefox это не работает. Это сказано в демонстрационной ссылке. Неожиданное значение NaN синтаксического анализа dy атрибута. svgtext_clean2.htm: 117 пытаюсь найти обходной путь.
Акшайб
Впоследствии я заставил его работать в Firefox. Вот, пожалуйста:
MSC
1
(Только что нажал ENTER слишком рано.) Впоследствии я заставил его работать в Firefox и IE. Если вам нужна помощь, загляните на сайт Democra.me/wrap_8_may_2014.htm . В коде есть комментарий о Firefox.
MSC
Как видите, я значительно расширил код, чтобы сузить ограничивающую рамку вверх или вниз или усечь многоточие в нужном месте.
MSC
Я бы изменил строку в коде MSC:, boxwidth = parseInt(boxObject.getAttribute('width'))просто принимал бы ширину в пикселях, в то время как boxwidth = parseInt(boxObject.getBBox().width)принимал бы любые единицы измерения
Массимилиано Канипароли
7

Следующий код работает нормально. Запустите фрагмент кода, что он делает.

Может быть, его можно очистить или заставить его автоматически работать со всеми текстовыми тегами в 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>

Питер
источник
1
Автоматический перенос строк в тексте SVG :) Мой код javascript создает строки, когда текст слишком длинный. Было бы хорошо, если бы я работал со всеми текстовыми тегами внутри SVG. автоматически без изменения id = "" в javascript. Плохой SVG сам по себе многострочный.
Питер
Хорошее решение, но можно выровнять по центру?
Крешимир Галич
Следует принять ответ tbh. Решение javascript достаточно минимально и имеет смысл.
Зак,
4

Я разместил здесь следующее пошаговое руководство по добавлению ложного переноса слов в «текстовый» элемент SVG:

SVG Word Wrap - Показать пробку?

Вам просто нужно добавить простую функцию JavaScript, которая разбивает вашу строку на более короткие элементы «tspan». Вот пример того, как это выглядит:

Пример SVG

Надеюсь это поможет !

Майк Гледхилл
источник