Ir para o conteúdo
OneKitly

Onde uma linha pode quebrar: o algoritmo Unicode por trás de cada parágrafo ajustado

Publicado a 01/07/2025 · 13 min de leitura · Ferramentas de texto e idioma

Daniel Okonkwo

Daniel OkonkwoProgramador front-end e redator de Tecnologia na OneKitly

Desempenho web · Formatos de ficheiro

Verificado a partir de 5 fontes

Ver perfil
Em resumo

Quebrar texto cortando pelos espaços é errado na maioria dos sistemas de escrita, e o Unicode especifica o procedimento correto: UAX #14, o algoritmo de quebra de linha. Atribui a cada ponto de código uma classe de quebra e depois aplica regras numeradas a cada par de classes. Um espaço inquebrável U+00A0 e um espaço fino inquebrável U+202F são da classe GL, cola, e a quebra depois deles é proibida: uma implementação conforme não encontrou nenhuma oportunidade de quebra dentro de cost·100·USD escrito com espaços inquebráveis, onde a versão vulgar tem duas. Um hífen condicional U+00AD é da classe BA, portanto a quebra ali é permitida e o hífen só se torna visível se for usada. Um espaço de largura zero U+200B é da classe ZW: permite a quebra e não mostra nada, que é como se quebra um texto sem espaço nenhum. Um hífen inquebrável U+2011 é cola, enquanto o hífen-menos vulgar U+002D é da classe HY e permite quebrar a seguir. O japonês não precisa de espaços: cada ideograma é da classe ID e a quebra é permitida entre quase quaisquer dois. O tailandês é da classe SA e não tem nenhuma sem análise por dicionário.

«Quebrar nos espaços» falha na maioria dos sistemas de escrita. O UAX #14 dá a cada caráter uma classe de quebra de linha; procurámos as nossas no Unicode 17.0.0 e executámos uma implementação conforme sobre espaços inquebráveis, hifenes condicionais, espaços de largura zero, URL, japonês e tailandês.

«Quebrar nos espaços» descreve uma só família de escritas

Corte pelo espaço e uma frase japonesa volta como um único token: 日本語のテキストは、空白で区切らない não tem espaço nenhum, nem o equivalente tailandês. Ambas se ajustam perfeitamente no ecrã, porque a regra não fala de espaços. Passe uma implementação conforme sobre essa frase japonesa: reporta dezasseis oportunidades de quebra em dezoito carateres — uma entre quase cada par — recusando quebrar antes da vírgula ideográfica ou do ponto. A mesma implementação sobre a frase tailandesa reporta zero, por uma razão a que voltamos mais abaixo.

O caso da escrita latina também não é seguro. Escrito com espaços vulgares, cost 100 USD oferece duas oportunidades de quebra; escrito com espaços inquebráveis U+00A0 não oferece nenhuma, e um quebrador por espaços devolve-o como um único token longo que não consegue partir. Os carateres parecem idênticos. A diferença vive nos pontos de código, e qualquer quebrador que leia só as formas produzirá uma linha demasiado longa ou uma quebra tipograficamente errada.

UAX #14: uma classe por caráter e depois regras sobre pares

A base de dados de carateres Unicode inclui um ficheiro LineBreak.txt que atribui uma classe de duas letras a cada ponto de código. Analisámos a versão atual — LineBreak-17.0.0.txt, de 29 de julho de 2025 — e define 49 classes distintas em 3 654 intervalos. A maior de longe é ID, ideográfica, com 172 561 pontos de código; AL, alfabética vulgar, cobre 26 954; as interessantes são minúsculas, GL com 41 pontos de código, QU com 39, WJ com 2 e ZW com exatamente 1. O algoritmo percorre depois a cadeia e consulta uma regra para cada par de classes adjacentes.

As regras são numeradas e divididas em dois grupos. Umas poucas não são modificáveis: uma implementação não pode alterá-las e continuar a alegar conformidade. As restantes, a partir de LB12a, são um valor por omissão razoável que uma implementação pode melhorar. A LB12 proíbe quebrar depois de cola. A LB13 proíbe-o antes do parêntesis reto de fecho, do ponto de exclamação, da barra e da classe de pontuação de fecho, mesmo depois de espaços. A LB18 permite quebrar depois de um espaço. A LB21 proíbe-o antes de um hífen ou de um caráter de quebra posterior. Quando os pares colidem, ganha a regra de número mais baixo, e é por isso que a especificação é uma lista e não uma tabela.

Cinco carateres invisíveis que decidem onde caem as linhas

O espaço inquebrável e o espaço fino inquebrável são ambos da classe GL. Ocupam largura e proíbem a quebra, que é o que mantém um número com a sua unidade e um tratamento com o seu apelido. O unificador de palavras U+2060 é da classe WJ e vai mais longe: nem quebra antes nem depois, e largura nenhuma. O espaço de largura zero U+200B é o contrário: classe ZW, sem largura, quebra permitida. E o hífen condicional U+00AD é da classe BA, um caráter de quebra posterior que não desenha nada até a quebra ser usada, altura em que o motor de desenho fornece um hífen.

O ensaio do hífen condicional torna o comportamento concreto. Pegámos num composto alemão de 63 carateres e inserimos cinco hifenes condicionais: comprimento armazenado 68, comprimento visível 63. Com largura 99 aparece como uma só palavra inteira de 63 carateres. Com largura 30 cabe em três linhas, com largura 20 em quatro, e o hífen só aparece no fim de cada linha quebrada — nunca no meio. Retire os hifenes condicionais e a mesma palavra com largura 20 volta como uma única linha de 63 carateres que transborda, porque não há sítio nenhum onde o algoritmo possa quebrar.

O mesmo truque salva escritas que o algoritmo não sabe segmentar sozinho. O tailandês é da classe SA, dependente de contexto complexo, e o UAX #14 diz claramente que séries destes carateres exigem análise morfológica, que sem ela não se encontrará oportunidade de quebra nenhuma e que uma implementação sem ela deve tratá-los como letras vulgares. A nossa implementação reportou zero oportunidades numa frase tailandesa de 23 carateres. Inserir três espaços de largura zero nas fronteiras de palavra produziu exatamente três — invisíveis no texto desenhado, decisivas para a quebra.

Os URL quebram em sítios que não escolheu

Dê um URL ao algoritmo e a resposta é mais interessante do que «em lado nenhum» ou «em qualquer lado». O nosso endereço de teste produziu sete oportunidades de quebra: depois da barra dupla, depois de cada barra simples, depois de cada hífen do caminho e depois do ponto de interrogação. Nunca antes de uma barra — a LB13 proíbe-o em absoluto, mesmo depois de um espaço — e nunca nos pontos do nome de anfitrião, porque o ponto é da classe IS e a LB15d recusa quebrar antes dele. O resultado é que um URL quebrado mantém os seus separadores no fim da linha, onde o leitor vê que a linha continua.

O perigo é o que faz com essa quebra. Uma quebra suave é uma decisão de apresentação: a cadeia em memória não muda, e recompor o parágrafo noutra largura apenas move a quebra. Uma quebra dura insere um caráter de mudança de linha real nos dados. Quebrámos duramente o URL de teste a 24 colunas; voltando a unir as linhas sem nada, o URL volta byte a byte, mas unindo-as com um espaço — que é o que fará um cliente de correio, uma aplicação de conversa ou uma cópia descuidada — obtêm-se quatro fragmentos separados por espaços, e a ligação está morta. Nunca quebre duramente algo que tenha de continuar a ser um único token.

A quebra dura destrói a recomposição — medido

Quebrámos um parágrafo a 32 colunas, guardámos o resultado com mudanças de linha reais e depois voltámos a quebrar a 20 sem o desfazer primeiro. A saída foram sete linhas, várias bem mais curtas do que 20 carateres, porque cada linha dura foi requebrada isoladamente e os finais irregulares não puderam ser preenchidos. Desfazendo primeiro — unindo as linhas com um espaço — e quebrando depois a 20 saíram cinco linhas cheias. Essa diferença é o custo da quebra dura: o texto já não sabe que fins de linha eram do autor e quais do desenhador anterior.

É também daí que vem a famosa largura fixa do correio eletrónico, e o número que se cita não é o da especificação. A RFC 5322 diz que cada linha NÃO DEVE exceder 998 carateres e NÃO DEVERIA exceder 78, sem contar o CRLF; os 998 existem porque implementações de transporte recusam mais de 1 000 carateres por linha, e os 78 porque o software de apresentação estraga tudo o que for mais longo. A RFC 3676, que define format=flowed, repete os 78 e explica que não são 79 nem 80 porque a última coluna costuma ficar reservada a um indicador de quebra. Os 72 que os clientes de correio usavam de facto são uma convenção por cima, que deixa espaço para vários níveis de marcas de citação nas respostas.

Format=flowed é o compromisso que tornou o correio de largura fixa suportável: uma linha terminada num espaço antes do seu CRLF é uma quebra suave que o leitor pode recompor, ao passo que uma linha sem esse espaço é a do autor. É texto vulgar para um cliente que nunca ouviu falar dele e texto recomponível para um que ouviu. A lição geral vai além do correio: guarde o parágrafo, não as linhas, e deixe o último desenhador decidir onde caem as quebras.

O que o CSS deixa mudar e o que não

O CSS Text Level 3 expõe o algoritmo através de cinco propriedades, e vale a pena conhecer os seus conjuntos de valores exatos. white-space aceita normal, pre, nowrap, pre-wrap, break-spaces ou pre-line, e vale normal por omissão. word-break aceita normal, keep-all, break-all ou break-word. line-break aceita auto, loose, normal, strict ou anywhere, e controla com que severidade o algoritmo se aplica à pontuação do Leste Asiático. hyphens aceita none, manual ou auto, com manual por omissão. overflow-wrap aceita normal, break-word ou anywhere. A especificação cita o UAX #14 pelo nome para as classes subjacentes.

Dois pormenores dessa lista são fáceis de errar. Como hyphens vale manual por omissão, os hifenes condicionais já presentes no seu texto são respeitados sem CSS nenhum — e hyphens: none suprime-os, mas a especificação diz explicitamente que isso não suprime as oportunidades de quebra oferecidas por carateres visíveis como U+002D e U+2010. E overflow-wrap: anywhere é um último recurso, não uma correção: permite quebrar em qualquer caráter quando uma palavra transbordaria, o que para o composto de 63 carateres acima significa uma quebra no meio de um morfema onde um hífen condicional teria dado uma correta.

Os espaços tipográficos não são decoração

A pontuação francesa é a demonstração mais clara de que estes pontos de código fazem trabalho a sério. Quebrámos duas vezes a mesma frase francesa: uma com espaços vulgares por todo o lado, outra com os espaços finos inquebráveis corretos antes da pontuação alta e um espaço inquebrável antes da unidade. A primeira versão oferecia catorze oportunidades de quebra, incluindo uma entre um número e o seu grupo de milhares e outra antes de uma aspa angular de fecho. A segunda oferecia nove, e nenhuma separava um sinal da palavra a que pertence. Mesmo texto visível, conjunto diferente de quebras legais.

O mesmo raciocínio vai muito além do francês. Um número e a sua unidade, uma abreviatura e o nome que a segue, um algarismo e um sinal de percentagem, um ordinal e o seu substantivo: todos se leem mal se uma quebra cair entre eles, e todos se resolvem com um caráter invisível em vez de uma regra no quebrador. A nossa ferramenta de quebra deixa esses carateres intactos, que é o único comportamento correto: limpá-los, como faz um normalizador de espaços ingénuo, dá em silêncio ao desenhador permissão para quebrar onde o autor a proibia.

Classes de quebra consultadas em LineBreak-17.0.0.txt (Unicode 17.0.0, datado de 29/07/2025) e oportunidades de quebra produzidas por uma implementação UAX #14 no Node 26.3.0. «Quebra depois?» descreve o comportamento em texto corrido; o algoritmo completo tem cerca de quarenta regras numeradas e algumas dependem da classe vizinha.
CaráterPonto de códigoClasseQuebra depois?Visível?
EspaçoU+0020SPSimSim, como largura em branco
Espaço inquebrávelU+00A0GLNãoSim, como largura em branco
Espaço fino inquebrávelU+202FGLNãoSim, mais estreito
Hífen-menosU+002DHYSimSempre
Hífen inquebrávelU+2011GLNãoSempre
Hífen condicionalU+00ADBASimSó se a quebra for usada
Espaço de largura zeroU+200BZWSimNunca
Unificador de palavrasU+2060WJNão, nem antes deleNunca
Barra num URLU+002FSYSim depois, nunca antes (LB13)Sempre
Ideograma CJKU+4E00 e mais 172 560IDSim, entre quase quaisquer doisSempre
Letra tailandesaU+0E01SASó com análise por dicionárioSempre
Quebra de linha automáticaAjuste as linhas longas a uma largura máxima sem cortar palavras.Experimentar a ferramenta

Perguntas frequentes

Porque é que o meu texto quebra no meio de um número?
Porque o separador de grupos é um espaço vulgar, classe SP, e a regra LB18 permite quebrar depois de qualquer espaço. Substitua-o por um espaço inquebrável U+00A0 ou um espaço fino inquebrável U+202F, ambos da classe GL, e a oportunidade desaparece: a nossa frase francesa passou de catorze oportunidades de quebra para nove com os espaços tipográficos, e nenhuma das nove restantes caía dentro de um número.
Qual é a diferença entre um hífen condicional e um espaço de largura zero?
Ambos permitem quebrar e nenhum é visível até ser usado, mas o hífen condicional U+00AD deixa um hífen quando a quebra acontece e o espaço de largura zero U+200B não deixa nada. Use o hífen condicional dentro de uma palavra, onde o hífen é a tipografia correta; use o espaço de largura zero entre unidades que não devem ganhar hífen, como partes de um identificador longo ou de um URL, e em escritas que nunca hifenizam.
Devo quebrar duramente o texto a 72 ou 80 carateres?
Só quando um protocolo o exigir. A RFC 5322 fixa um limite duro de 998 carateres por linha e uma recomendação de 78; os 72 que os clientes de correio usavam deixam espaço para as marcas de citação nas respostas e são uma convenção, não uma especificação. A quebra dura custa a recomposição: requebrar a 20 um parágrafo já quebrado duramente a 32 colunas deu sete linhas irregulares, contra cinco linhas cheias ao desfazê-lo primeiro.
Como evito que um URL longo transborde do seu contentor?
Não inserindo mudanças de linha. O algoritmo já oferece quebras depois da barra dupla, depois de cada barra e depois de cada hífen — sete oportunidades no nosso endereço de teste — pelo que uma quebra suave costuma bastar. Se o contentor for mais estreito do que a maior sequência inquebrável, acrescente overflow-wrap: anywhere para esse elemento ou insira espaços de largura zero, que nada custam ao copiar. Um URL quebrado duramente e reunido com um espaço já não é um URL.
O algoritmo sabe onde acabam as palavras tailandesas?
Não, e o UAX #14 di-lo. Os carateres tailandeses, laosianos e khmer são da classe SA, dependente de contexto complexo, e o anexo indica que as suas séries exigem análise morfológica, que sem ela não se encontrará oportunidade de quebra nenhuma e que uma implementação sem ela deve tratá-los como letras vulgares. A nossa implementação conforme encontrou zero oportunidades numa frase tailandesa de 23 carateres, e exatamente três depois de inserir três espaços de largura zero.

Artigos que podem interessar-lhe

Todos os guias
GuiaConverter entre formatos de lista sem perder dados: as regras de aspas que ninguém lêPassar de uma lista com quebras de linha para uma lista com vírgulas é trivial até um item conter uma vírgula. As regras de aspas da RFC 4180, porque um campo CSV pode conter uma quebra de linha, porque as folhas de cálculo europeias usam o ponto e vírgula, e o que um item vazio faz à ida e volta — cada caso executado e impresso.ExplicaçãoFrequência de palavras e lei de Zipf: contámos seis livros em seis línguas e ajustámos o decliveA palavra de posição n aparece cerca de 1/n vezes em relação à primeira. Contámos seis livros de domínio público, imprimimos posição × frequência, ajustámos log frequência contra log posição e obtivemos declives entre -1,02 e -1,08 nas seis línguas — e os dois pontos onde a lei falha.ExplicaçãoContar palavras é ambíguo, e cada ferramenta responde de forma diferenteUma contagem de palavras é uma definição, não uma medição. Contámos o mesmo parágrafo de quatro maneiras e obtivemos 25, 28, 33 e 38; depois contámos 50 000 caracteres de prosa vulgar e obtivemos acordo dentro de 4,5 %. A diferença deve-se inteiramente a compostos, algarismos e URL.ExplicaçãoO que medem realmente os índices de legibilidade (e as três coisas que não veem)O Flesch Reading Ease e o nível Flesch-Kincaid contam sílabas e comprimento de frase. Mais nada. As duas fórmulas na íntegra, uma passagem pontuada de ponta a ponta e o truque da vírgula que oferece 3,9 anos de escolaridade sem mudar uma palavra.ExplicaçãoA densidade de palavras-chave é uma métrica morta, e eis o que a substituiuA densidade contava ocorrências porque a recuperação contava ocorrências. TF-IDF, depois BM25 com a sua curva de saturação, depois os embeddings substituíram-na. Eis a mesma página de 800 palavras pontuada de três formas, e porque é que as três divergem.TutorialFiltrar linhas por um padrão sem linha de comandosIsto é o grep para quem não usa grep, com uma diferença importante: a procura é uma subcadeia literal, pelo que uma expressão regular a sério devolve uma caixa vazia e nenhum erro. Cada afirmação foi verificada executando a ferramenta.

Ferramentas relacionadas

Fontes

Detetaste um erro neste artigo?

Onde uma linha pode quebrar: o algoritmo Unicode por trás de cada parágrafo ajustado — OneKitly