> [Site index](https://perfectvector.com/llms.txt) — available pages and descriptions.

# Quebra de texto SVG: crie linhas medidas com tspan

> Quebre rótulos SVG medindo palavras com a fonte real e gerando tspans. Teste rótulos estreitos, rejeite palavras ou linhas excedentes e salve o resultado.

Source: https://perfectvector.com/pt-BR/blog/svg-text-wrap-tspan

Language: pt-BR
Author: Irene Kim
Published: 2026-10-06T08:00:00+09:00
Updated: 2026-10-06T08:00:00+09:00

Para quebrar um rótulo SVG variável em linhas explícitas, meça grupos candidatos de palavras com `getComputedTextLength()` e escreva um `<tspan>` por linha aceita. Aguarde primeiro as fontes do documento e decida o que acontece quando uma palavra é mais larga que o rótulo ou o resultado excede seu limite de linhas.

O exemplo abaixo usa palavras separadas por espaços em branco e rejeita qualquer um desses excessos sem emitir um rótulo parcial. Mantém uma ilustração original de losango vazado separada do texto, portanto mudar a largura do rótulo não redesenha a arte.

## Escolha uma política de quebra antes de posicionar as linhas

O [elemento `tspan`](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/tspan) permite posicionar trechos de texto SVG. Nossa rotina define o mesmo `x` em cada linha e um deslocamento `dy` nas seguintes. A rotina decide as quebras; o elemento guarda o resultado.

Esta é uma abordagem explícita de layout. A [especificação de texto SVG 2](https://www.w3.org/TR/SVG2/text.html) também define quebra automática por uma área de conteúdo e layout de texto CSS. Este artigo não testa esses recursos nem afirma que todos os navegadores não os têm.

Para um parágrafo HTML fora da ilustração, [faça o texto contornar uma silhueta SVG flutuante com CSS](https://perfectvector.com/pt-BR/blog/css-text-around-svg-shape). Esse layout segue um contorno de imagem e mantém o texto como HTML comum, em vez de transformá-lo em linhas posicionadas de rótulo SVG.

Para este pequeno exemplo de rótulo em inglês, a política é:

* Reduzir espaços em branco e testar palavras na ordem original, separadas por um espaço.
* Manter uma linha candidata só enquanto o comprimento medido cabe na largura disponível.
* Rejeitar uma palavra que não cabe sozinha; não inventar um ponto de hifenização.
* Rejeitar o resultado inteiro quando exige mais que `maxLines`; não descartar silenciosamente as últimas palavras.

Um limite de linhas é uma decisão de layout. Escolha-o junto da linha de base inicial, do espaçamento e da altura disponível. O quebrador não calcula uma caixa vertical de conteúdo por você.

**Mude a largura do rótulo, mantenha a arte**

![Comparação conceitual de uma ilustração de losango vazado ao lado de linhas longas em um rótulo largo e linhas curtas em um rótulo estreito](https://perfectvector.com/blog/svg-text-wrap-tspan/measured-lines.webp)

*Ilustração: um rótulo mais estreito pode exigir mais linhas. Barras abstratas e quadros pontilhados são indicações conceituais; o texto real, as medidas e os estados de rejeição abaixo vêm do exemplo independente no navegador.*

## Meça com a fonte que desenhará o rótulo

[`getComputedTextLength()`](https://developer.mozilla.org/en-US/docs/Web/API/SVGTextContentElement/getComputedTextLength) mede o comprimento calculado do texto SVG. Este exemplo clona o elemento de texto do rótulo como uma sonda oculta no mesmo SVG anexado e remove a sonda após planejar as linhas. Ambos usam `Arial, sans-serif` explícito a 22 unidades.

O código aguarda [`document.fonts.ready`](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) antes de medir. Isso aguarda o carregamento das fontes do documento e o layout associado; não incorpora uma fonte no SVG exportado. Outra fonte disponível pode produzir comprimentos diferentes. Refaça o layout quando o texto, as configurações de fonte ou a largura disponível mudarem.

## Execute o exemplo completo sem dependências

Salve como `starter.html` e abra em um navegador. Ele cria seis casos, imprime suas medidas e serializa cada SVG resultante. Os dois primeiros mudam apenas a largura do rótulo; o terceiro muda o texto. Os demais exercitam rejeição e texto literal.

```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Measured SVG label lines</title>
<style>
  body { margin: 24px; font: 16px system-ui; background: #f6f2e8; }
  #cases { display: grid; grid-template-columns: repeat(2, 360px); gap: 20px; }
  figure { margin: 0; }
  svg { display: block; background: white; }
  figcaption { margin: 6px 0; }
  pre { white-space: pre-wrap; font-size: 12px; }
</style>
<h1>Keep artwork and label layout separate</h1>
<div id="cases"></div>
<pre id="report"></pre>
<script>
  const ns = 'http://www.w3.org/2000/svg';
  const emblem = 'M60 32 L100 72 L60 112 L20 72 Z M60 54 L78 72 L60 90 L42 72 Z';
  function element(name, attributes) {
    const node = document.createElementNS(ns, name);
    for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, value);
    return node;
  }
  function wrapWords(text, source, { width, maxLines, lineHeight }) {
    text.replaceChildren();
    if (!(width > 0 && lineHeight > 0 && Number.isInteger(maxLines) && maxLines > 0)) {
      return { status: 'invalid-options', lines: [] };
    }
    const words = source.trim().split(/\s+/u).filter(Boolean);
    const probe = text.cloneNode(false);
    probe.setAttribute('visibility', 'hidden');
    text.parentNode.append(probe);
    const measure = value => { probe.textContent = value; return probe.getComputedTextLength(); };
    const lines = [];
    let line = '';
    try {
      for (const word of words) {
        const wordWidth = measure(word);
        if (wordWidth > width) {
          return { status: 'overlong-word', word, measuredWidth: wordWidth, lines: [] };
        }
        const candidate = line ? line + ' ' + word : word;
        if (line && measure(candidate) > width) {
          lines.push(line);
          line = word;
        } else line = candidate;
      }
      if (line) lines.push(line);
      if (lines.length > maxLines) {
        return { status: 'too-many-lines', requiredLines: lines.length, lines: [] };
      }
      const x = text.getAttribute('x');
      lines.forEach((value, index) => {
        const span = element('tspan', { x, dy: index ? lineHeight : 0 });
        span.textContent = value;
        text.append(span);
      });
      return { status: 'ok', lines,
        lengths: [...text.children].map(span => span.getComputedTextLength()) };
    } finally { probe.remove(); }
  }
  const cases = [
    { id: 'normal', source: 'Keep artwork and labels separate', width: 200, maxLines: 4 },
    { id: 'narrow', source: 'Keep artwork and labels separate', width: 110, maxLines: 5 },
    { id: 'changed', source: 'Keep the original artwork beside every exported label', width: 200, maxLines: 4 },
    { id: 'long-word', source: 'Artwork supercalifragilisticexpialidocious', width: 110, maxLines: 5 },
    { id: 'line-limit', source: 'Keep artwork and labels separate', width: 110, maxLines: 2 },
    { id: 'literal', source: 'Cove <g> & field', width: 200, maxLines: 4 },
  ];
  const entries = cases.map(test => {
    const figure = document.createElement('figure');
    const svg = element('svg', { width: 360, height: 240, viewBox: '0 0 360 240' });
    svg.append(element('path', { d: emblem, fill: '#176b5b', 'fill-rule': 'evenodd' }));
    svg.append(element('rect', { x: 140, y: 34, width: test.width, height: 170,
      fill: 'none', stroke: '#ce7b55', 'stroke-dasharray': '4 4' }));
    const text = element('text', { x: 140, y: 60, 'font-family': 'Arial, sans-serif',
      'font-size': 22, fill: '#172b34' });
    svg.append(text);
    const caption = document.createElement('figcaption');
    figure.append(svg, caption);
    document.getElementById('cases').append(figure);
    return { test, svg, text, caption };
  });
  async function run() {
    await document.fonts.ready;
    const results = entries.map(({ test, svg, text, caption }) => {
      const result = wrapWords(text, test.source, { ...test, lineHeight: 30 });
      caption.textContent = test.id + ': ' + result.status;
      return { ...test, ...result, fontFamily: getComputedStyle(text).fontFamily,
        fontSize: getComputedStyle(text).fontSize, tspanCount: text.children.length,
        pathD: svg.querySelector('path').getAttribute('d'),
        pathCount: svg.querySelectorAll('path').length,
        imageCount: svg.querySelectorAll('image').length,
        foreignObjectCount: svg.querySelectorAll('foreignObject').length,
        serialized: new XMLSerializer().serializeToString(svg) };
    });
    document.getElementById('report').textContent = JSON.stringify({
      fontStatus: document.fonts.status, policy: 'Whitespace words; reject a long word or too many lines without partial output', results,
    }, null, 2);
  }
  run();
</script>
</html>
```

A rotina cria cada linha com [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent), em vez de inserir o texto como marcação. No caso literal, `Cove <g> & field` continua texto comum: o SVG serializado contém sinais de menor e maior e um e comercial escapados, não um elemento de grupo extra.

O clone de medição é anexado, mas fica oculto, com os mesmos atributos de fonte do rótulo visível. Ele é removido em `finally`, inclusive na rejeição. As linhas só são anexadas depois de passar nas verificações de largura das palavras e máximo de linhas. A rotina limpa primeiro qualquer rótulo anterior, então uma atualização rejeitada não deixa texto antigo ou parcial; trate o estado retornado na interface da aplicação.

## O que os seis casos originais produziram

Executamos exatamente este exemplo no Chrome com as fontes do documento informando `loaded`. Os comprimentos abaixo são comprimentos de texto SVG desse ambiente, não limites de glifos pintados nem garantia de portabilidade de fonte.

| Caso                                         | Largura / máximo de linhas | Resultado                                          | Maior linha observada    |
| -------------------------------------------- | -------------------------- | -------------------------------------------------- | ------------------------ |
| Frase original                               | 200 / 4                    | Duas linhas: Keep artwork and; labels separate     | 172.44                   |
| Mesma frase, caixa estreita                  | 110 / 5                    | Quatro linhas: Keep; artwork; and labels; separate | 100.30                   |
| Texto mais longo                             | 200 / 4                    | Três linhas                                        | 198.13                   |
| Palavra longa demais                         | 110 / 5                    | overlong-word; zero tspans                         | Palavra medida em 311.82 |
| Frase estreita, limite de duas linhas        | 110 / 2                    | too-many-lines; exigiu quatro; zero tspans         | Nenhum rótulo emitido    |
| Sinais de menor/maior e e comercial literais | 200 / 4                    | Uma linha literal                                  | 162.66                   |

A frase original é `Keep artwork and labels separate`. A versão mais longa é `Keep the original artwork beside every exported label`. Cada linha aceita mediu dentro da largura escolhida. Os casos de rejeição deixaram a ilustração visível sem o rótulo, facilitando examinar a falha.

Os seis arquivos serializados mantiveram o caminho original idêntico do losango, um elemento de texto nativo e nenhuma imagem ou `foreignObject`. Os casos aceitos continham dois, quatro, três e um tspans, respectivamente; os rejeitados, zero. Também reabrimos os seis SVGs serializados como imagens no mesmo navegador, onde todos carregaram a 360×240. É uma verificação desses arquivos e desse navegador, não de todo editor ou renderizador.

## Salve o SVG diagramado e teste a entrega

[`XMLSerializer.serializeToString()`](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer/serializeToString) cria a marcação serializada no relatório. Salve a string `serialized` de um caso bem-sucedido como arquivo `.svg`. O arquivo já contém linhas explícitas; não precisa dessa rotina JavaScript para recriá-las.

As palavras continuam texto ativo. O arquivo salvo mantém a solicitação de família de fonte, não um arquivo de fonte ou glifos contornados. Um destino com outra fonte pode mudar as letras mesmo que as strings e posições das linhas permaneçam. Mantenha o texto original e as entradas de layout junto da cópia exportada e examine o destino real antes da entrega. Para formas fixas de letras, faça uma cópia de entrega em contornos em um editor adequado, preservando o texto ativo no original mestre.

O [guia de texto foreignObject](https://perfectvector.com/pt-BR/blog/svg-foreignobject-text) trata de rótulos HTML que desaparecem em outro renderizador. Este exemplo cria texto nativo desde o início. O [guia de textLength](https://perfectvector.com/pt-BR/blog/svg-textlength-fit) cobre ajustar uma única linha pelo comprimento; é outra decisão, diferente de adicionar linhas medidas. Use o [guia de alinhamento vertical](https://perfectvector.com/pt-BR/blog/svg-text-vertical-alignment) para posicionar o rótulo inteiro em relação à arte ao redor.

## Explicite os limites do quebrador

Dividir por espaços em branco é uma política deliberadamente pequena. Ela reduz espaços repetidos, tabulações e quebras explícitas e não trata regras de quebra específicas de idioma, hifenização, parágrafos bidirecionais ou trechos ricos com estilos mistos. Escolha outro sistema de layout ou amplie e teste a política quando seu texto exigir esses recursos.

A verificação de largura também não garante que prolongamentos decorativos de glifos fiquem dentro de um quadro desenhado nem que cinco linhas de base caibam na altura escolhida. Examine as letras visíveis no tamanho de entrega. Nosso arquivo usa rótulos horizontais, atributos fixos de fonte e passo de linha de 30 unidades; não transfira as medidas para outra fonte sem medir novamente.

Se sua ilustração existe só como PNG ou JPG e você precisa de arte editável, [prepare um SVG de clipart com o PerfectVector](https://perfectvector.com/pt-BR/clipart-to-svg), examine bordas e aberturas internas e mantenha a arte separada do rótulo. Redigite o texto como texto nativo, execute a política de quebra e examine a exportação montada. A vetorização prepara caminhos da ilustração; não escolhe quebras nem recupera palavras editáveis de letras traçadas. Caminhos bons existentes podem continuar como estão. O [guia de vetorização](https://perfectvector.com/pt-BR/blog/what-is-image-vectorization) explica essa recuperação da origem.

## Perguntas frequentes

**tspan decide automaticamente onde quebrar cada linha?**
Este exemplo decide medindo grupos de palavras e cria um tspan posicionado para cada linha aceita. Não testa a quebra automática baseada em CSS do SVG 2.

**O que acontece com uma palavra mais larga que o rótulo?**
A rotina retorna overlong-word e não emite rótulo. Amplie a área, encurte o texto ou forneça uma política de quebra testada separadamente em vez de cortar silenciosamente.

**maxLines remove as palavras restantes?**
Não. Esta política rejeita um resultado que exige linhas demais, informa a quantidade necessária e deixa zero tspans. A aplicação precisa tratar esse estado.

**As linhas exportadas ficarão idênticas com outra fonte?**
As palavras e posições explícitas permanecem, mas o arquivo não incorpora fonte nem contorna glifos. Examine a fonte disponível e as letras no destino real.

## Fontes

1. [MDN elemento tspan](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/tspan) — trechos posicionados e atributos de deslocamento de linha.
2. [W3C texto SVG 2](https://www.w3.org/TR/SVG2/text.html) — posicionamento explícito e o modelo separado de quebra automática.
3. [MDN getComputedTextLength](https://developer.mozilla.org/en-US/docs/Web/API/SVGTextContentElement/getComputedTextLength) — medição do texto SVG real.
4. [MDN FontFaceSet.ready](https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet/ready) — espera por carregamento de fontes e layout.
5. [MDN textContent](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) — inserção de texto simples em vez de marcação.
6. [MDN XMLSerializer](https://developer.mozilla.org/en-US/docs/Web/API/XMLSerializer/serializeToString) — salvamento da marcação SVG diagramada.

***

Para sua própria ilustração raster, [prepare arte de clipart editável](https://perfectvector.com/pt-BR/clipart-to-svg), examine as formas e mantenha o original mestre. Adicione o texto separadamente e verifique as linhas aceitas e as letras visíveis no SVG salvo antes da entrega.

## Explore

- [Read the HTML article](https://perfectvector.com/pt-BR/blog/svg-text-wrap-tspan)
- [SVG.js: importe arte, edite um caminho e exporte SVG](https://perfectvector.com/pt-BR/blog/svgjs-import-export-artwork.md)
- [Marcadores SVG Leaflet: alinhe a ponta com iconAnchor](https://perfectvector.com/pt-BR/blog/leaflet-svg-marker-anchor.md)
- [Product facts](https://perfectvector.com/product-facts.md)
- [FAQ](https://perfectvector.com/faq.md)
- [Pricing and credits](https://perfectvector.com/pricing.md)
