Dove una riga può andare a capo: l'algoritmo Unicode dietro ogni paragrafo mandato a capo
Pubblicato il 01/07/2025 · 13 min di lettura · Strumenti testo e lingua
Daniel Okonkwo — Sviluppatore front-end e redattore Tech presso Allin
Performance web · Formati di file
Verificato su 5 fonti
Mandare a capo il testo tagliando sugli spazi è sbagliato nella maggior parte dei sistemi di scrittura, e Unicode specifica la procedura corretta: UAX #14, l'algoritmo di interruzione di riga. Assegna a ogni punto di codice una classe di interruzione e poi applica regole numerate a ogni coppia di classi. Uno spazio unificatore U+00A0 e uno spazio stretto unificatore U+202F sono di classe GL, colla, e l'interruzione dopo di essi è vietata: un'implementazione conforme non ha trovato alcuna opportunità di interruzione dentro cost·100·USD scritto con spazi unificatori, dove la versione ordinaria ne ha due. Un trattino morbido U+00AD è di classe BA, quindi lì l'interruzione è permessa e il trattino diventa visibile solo se viene usata. Uno spazio a larghezza zero U+200B è di classe ZW: permette l'interruzione e non mostra nulla, ed è così che si manda a capo un testo senza alcuno spazio. Un trattino unificatore U+2011 è colla, mentre il trattino-meno ordinario U+002D è di classe HY e un'interruzione dopo di sé la consente. Il giapponese non ha bisogno di spazi: ogni ideogramma è di classe ID e l'interruzione è permessa fra quasi ogni coppia. Il thai è di classe SA e non ne ha nessuna senza analisi da dizionario.
«Andare a capo sugli spazi» fallisce nella maggior parte dei sistemi di scrittura. UAX #14 assegna a ogni carattere una classe di interruzione di riga; abbiamo cercato le nostre in Unicode 17.0.0 ed eseguito un'implementazione conforme su spazi unificatori, trattini morbidi, spazi a larghezza zero, URL, giapponese e thai.
«Andare a capo sugli spazi» descrive una sola famiglia di scritture
Taglia sullo spazio e una frase giapponese torna come un solo token: 日本語のテキストは、空白で区切らない non ha alcuno spazio, e nemmeno l'equivalente thai. Entrambe si mandano a capo benissimo sullo schermo, perché la regola non parla di spazi. Fai girare un'implementazione conforme su quella frase giapponese: riporta sedici opportunità di interruzione su diciotto caratteri — una fra quasi ogni coppia — rifiutandosi di interrompere prima della virgola ideografica o del punto. La stessa implementazione sulla frase thai ne riporta zero, per una ragione su cui torniamo più sotto.
Nemmeno il caso della scrittura latina è sicuro. Scritto con spazi ordinari, cost 100 USD offre due opportunità di interruzione; scritto con spazi unificatori U+00A0 non ne offre nessuna, e un impaginatore che taglia sugli spazi lo restituisce come un unico token lungo che non sa spezzare. I caratteri appaiono identici. La differenza vive nei punti di codice, e qualunque impaginatore che legga solo le forme produrrà una riga troppo lunga o un'interruzione tipograficamente sbagliata.
UAX #14: una classe per carattere, poi regole sulle coppie
Il database dei caratteri Unicode contiene un file LineBreak.txt che assegna una classe di due lettere a ogni punto di codice. Abbiamo analizzato la versione corrente — LineBreak-17.0.0.txt, datato 29 luglio 2025 — e definisce 49 classi distinte su 3 654 intervalli. La più grande di gran lunga è ID, ideografica, con 172 561 punti di codice; AL, alfabetica ordinaria, ne copre 26 954; quelle interessanti sono minuscole, GL con 41 punti di codice, QU con 39, WJ con 2 e ZW con esattamente 1. L'algoritmo scorre poi la stringa e consulta una regola per ogni coppia di classi adiacenti.
Le regole sono numerate e divise in due gruppi. Poche sono non adattabili: un'implementazione non può cambiarle e continuare a dichiararsi conforme. Le altre, da LB12a in poi, sono un default ragionevole che un'implementazione può migliorare. LB12 vieta l'interruzione dopo la colla. LB13 la vieta prima della parentesi quadra di chiusura, del punto esclamativo, della barra e della classe di punteggiatura di chiusura, anche dopo spazi. LB18 consente l'interruzione dopo uno spazio. LB21 la vieta prima di un trattino o di un carattere a interruzione successiva. Quando le coppie confliggono vince la regola col numero più basso, ed è per questo che la specifica è un elenco e non una tabella.
Cinque caratteri invisibili che decidono dove cadono le righe
Lo spazio unificatore e lo spazio stretto unificatore sono entrambi di classe GL. Occupano larghezza e vietano l'interruzione, ed è ciò che tiene un numero con la sua unità e un titolo con il suo cognome. L'unificatore di parole U+2060 è di classe WJ e va oltre: nessuna interruzione prima né dopo, e nessuna larghezza. Lo spazio a larghezza zero U+200B è il contrario: classe ZW, nessuna larghezza, interruzione permessa. E il trattino morbido U+00AD è di classe BA, un carattere a interruzione successiva che non disegna nulla finché l'interruzione non viene usata, momento in cui il motore di resa fornisce un trattino.
La prova col trattino morbido rende concreto il comportamento. Abbiamo preso un composto tedesco di 63 caratteri e inserito cinque trattini morbidi: lunghezza memorizzata 68, lunghezza visibile 63. A larghezza 99 appare come un'unica parola intera di 63 caratteri. A larghezza 30 sta su tre righe, a larghezza 20 su quattro, e il trattino compare solo alla fine di ogni riga spezzata — mai nel mezzo. Togli i trattini morbidi e la stessa parola a larghezza 20 torna come un'unica riga di 63 caratteri che trabocca, perché non c'è alcun punto in cui l'algoritmo possa interrompere.
Lo stesso trucco salva le scritture che l'algoritmo non sa segmentare da solo. Il thai è di classe SA, dipendente da contesto complesso, e UAX #14 dice chiaramente che sequenze di questi caratteri richiedono analisi morfologica, che senza di essa non si troverà alcuna opportunità di interruzione e che un'implementazione priva di tale analisi dovrebbe trattarli come lettere ordinarie. La nostra implementazione ha riportato zero opportunità in una frase thai di 23 caratteri. Inserire tre spazi a larghezza zero ai confini di parola ne ha prodotte esattamente tre — invisibili nel testo reso, decisive per l'impaginazione.
Gli URL si spezzano in punti che non hai scelto
Dai un URL all'algoritmo e la risposta è più interessante di «da nessuna parte» o «ovunque». Il nostro indirizzo di prova ha prodotto sette opportunità di interruzione: dopo la doppia barra, dopo ogni barra singola, dopo ogni trattino nel percorso e dopo il punto interrogativo. Mai prima di una barra — LB13 lo vieta senza appello, anche dopo uno spazio — e mai ai punti del nome host, perché il punto è di classe IS e LB15d rifiuta un'interruzione prima di esso. Il risultato è che un URL mandato a capo tiene i suoi separatori a fine riga, dove il lettore vede che la riga continua.
Il pericolo è cosa fai di quell'interruzione. Un'interruzione morbida è una decisione di visualizzazione: la stringa in memoria non cambia, e ricomporre il paragrafo a un'altra larghezza sposta semplicemente l'interruzione. Un'interruzione dura inserisce un vero carattere di fine riga nei dati. Abbiamo spezzato duramente l'URL di prova a 24 colonne; riunendo le righe senza nulla, l'URL torna byte per byte, ma riunendole con uno spazio — che è ciò che farà un client di posta, un'app di chat o una copia distratta — si ottengono quattro frammenti separati da spazi, e il collegamento è morto. Non spezzare mai duramente ciò che deve restare un unico token.
L'interruzione dura distrugge la ricomposizione — misurato
Abbiamo mandato a capo un paragrafo a 32 colonne, salvato il risultato con a capo reali e poi rimandato a capo a 20 senza prima ricomporlo. L'output erano sette righe, diverse ben più corte di 20 caratteri, perché ogni riga dura è stata rimandata a capo isolatamente e i bordi frastagliati non si potevano riempire. Ricomponendo prima — riunendo le righe con uno spazio — e poi impaginando a 20 sono uscite cinque righe piene. Quella differenza è il costo dell'interruzione dura: il testo non sa più quali fine riga erano dell'autore e quali del motore precedente.
Da qui viene anche la famosa larghezza fissa della posta elettronica, e il numero che si cita non è quello della specifica. La RFC 5322 dice che ogni riga NON DEVE superare i 998 caratteri e NON DOVREBBE superare i 78, esclusi i CRLF; i 998 esistono perché implementazioni di trasporto rifiutano più di 1 000 caratteri per riga, e i 78 perché il software di visualizzazione rovina tutto ciò che è più lungo. La RFC 3676, che definisce format=flowed, ripete i 78 e spiega che non sono 79 o 80 perché l'ultima colonna è spesso riservata a un indicatore di ritorno a capo. I 72 realmente usati dai client di posta sono una convenzione sopra a questo, che lascia spazio a più livelli di segni di citazione nelle risposte.
Format=flowed è il compromesso che ha reso sopportabile la posta a larghezza fissa: una riga che termina con uno spazio prima del suo CRLF è un'interruzione morbida che il lettore può ricomporre, mentre una riga senza quello spazio è dell'autore. È testo ordinario per un client che non ne ha mai sentito parlare, e testo ricomponibile per uno che lo conosce. La lezione generale va oltre la posta: memorizza il paragrafo, non le righe, e lascia che sia l'ultimo motore di resa a decidere dove cadono le interruzioni.
Cosa permette di cambiare il CSS, e cosa no
CSS Text Level 3 espone l'algoritmo tramite cinque proprietà, e vale la pena conoscerne gli insiemi di valori esatti. white-space accetta normal, pre, nowrap, pre-wrap, break-spaces o pre-line, e vale normal per default. word-break accetta normal, keep-all, break-all o break-word. line-break accetta auto, loose, normal, strict o anywhere, e regola quanto severamente l'algoritmo si applica alla punteggiatura dell'Asia orientale. hyphens accetta none, manual o auto, con manual per default. overflow-wrap accetta normal, break-word o anywhere. La specifica cita UAX #14 per nome per le classi sottostanti.
Due dettagli di quell'elenco sono facili da sbagliare. Poiché hyphens vale manual per default, i trattini morbidi già presenti nel tuo testo vengono rispettati senza alcun CSS — e hyphens: none li sopprime, ma la specifica dice esplicitamente che ciò non sopprime le opportunità di interruzione offerte da caratteri visibili come U+002D e U+2010. E overflow-wrap: anywhere è un'ultima risorsa, non una soluzione: consente un'interruzione a qualunque carattere quando una parola traboccherebbe, il che per il composto di 63 caratteri sopra significa un'interruzione in mezzo a un morfema dove un trattino morbido ne avrebbe data una corretta.
Gli spazi tipografici non sono decorazione
La punteggiatura francese è la dimostrazione più chiara che questi punti di codice fanno un lavoro reale. Abbiamo impaginato due volte la stessa frase francese: una con spazi ordinari ovunque, una con i corretti spazi stretti unificatori davanti alla punteggiatura alta e uno spazio unificatore davanti all'unità. La prima versione offriva quattordici opportunità di interruzione, fra cui una fra un numero e il suo gruppo di migliaia e una prima di una virgoletta angolare di chiusura. La seconda ne offriva nove, e nessuna separava un segno dalla parola a cui appartiene. Stesso testo visibile, insieme diverso di interruzioni lecite.
Lo stesso ragionamento va ben oltre il francese. Un numero e la sua unità, un'abbreviazione e il nome che segue, una cifra e un segno di percentuale, un ordinale e il suo sostantivo: tutti si leggono male se un'interruzione cade in mezzo, e tutti si risolvono con un carattere invisibile invece che con una regola nell'impaginatore. Il nostro strumento di impaginazione lascia intatti quei caratteri, l'unico comportamento corretto: ripulirli, come fa un normalizzatore di spazi ingenuo, dà in silenzio al motore di resa il permesso di interrompere dove l'autore lo vietava.
| Carattere | Punto di codice | Classe | Interruzione dopo? | Visibile? |
|---|---|---|---|---|
| Spazio | U+0020 | SP | Sì | Sì, come larghezza vuota |
| Spazio unificatore | U+00A0 | GL | No | Sì, come larghezza vuota |
| Spazio stretto unificatore | U+202F | GL | No | Sì, più stretto |
| Trattino-meno | U+002D | HY | Sì | Sempre |
| Trattino unificatore | U+2011 | GL | No | Sempre |
| Trattino morbido | U+00AD | BA | Sì | Solo se l'interruzione viene usata |
| Spazio a larghezza zero | U+200B | ZW | Sì | Mai |
| Unificatore di parole | U+2060 | WJ | No, e nemmeno prima | Mai |
| Barra in un URL | U+002F | SY | Sì dopo, mai prima (LB13) | Sempre |
| Ideogramma CJK | U+4E00 e altri 172 560 | ID | Sì, fra quasi ogni coppia | Sempre |
| Lettera thai | U+0E01 | SA | Solo con analisi da dizionario | Sempre |
Domande frequenti
- Perché il mio testo va a capo in mezzo a un numero?
- Perché il separatore di gruppi è uno spazio ordinario, classe SP, e la regola LB18 consente un'interruzione dopo qualunque spazio. Sostituiscilo con uno spazio unificatore U+00A0 o uno spazio stretto unificatore U+202F, entrambi di classe GL, e l'opportunità sparisce: la nostra frase francese è passata da quattordici opportunità di interruzione a nove con gli spazi tipografici, e nessuna delle nove restanti cadeva dentro un numero.
- Che differenza c'è fra un trattino morbido e uno spazio a larghezza zero?
- Entrambi permettono un'interruzione e nessuno dei due è visibile finché non viene usato, ma il trattino morbido U+00AD lascia un trattino quando l'interruzione avviene e lo spazio a larghezza zero U+200B non lascia nulla. Usa il trattino morbido dentro una parola, dove il trattino è la tipografia corretta; usa lo spazio a larghezza zero fra unità che non devono guadagnare un trattino, come parti di un identificatore lungo o di un URL, e nelle scritture che non usano mai il trattino.
- Devo spezzare duramente il testo a 72 o 80 caratteri?
- Solo quando un protocollo lo richiede. La RFC 5322 fissa un limite duro di 998 caratteri per riga e una raccomandazione di 78; i 72 usati dai client di posta lasciano spazio ai segni di citazione nelle risposte e sono una convenzione, non una specifica. L'interruzione dura costa la ricomposizione: rimandare a capo a 20 un paragrafo già spezzato duramente a 32 colonne ha dato sette righe frastagliate, contro cinque righe piene ricomponendolo prima.
- Come evito che un URL lungo trabocchi dal suo contenitore?
- Non inserendo a capo. L'algoritmo offre già interruzioni dopo la doppia barra, dopo ogni barra e dopo ogni trattino — sette opportunità nel nostro indirizzo di prova — quindi un'interruzione morbida di solito basta. Se il contenitore è più stretto della più lunga sequenza inspezzabile, aggiungi overflow-wrap: anywhere per quell'elemento o inserisci spazi a larghezza zero, che non costano nulla nella copia. Un URL spezzato duramente e riunito con uno spazio non è più un URL.
- L'algoritmo sa dove finiscono le parole thai?
- No, e UAX #14 lo dice. I caratteri thai, lao e khmer sono di classe SA, dipendente da contesto complesso, e l'annesso afferma che le loro sequenze richiedono analisi morfologica, che senza di essa non si troverà alcuna opportunità di interruzione e che un'implementazione che ne è priva dovrebbe trattarli come lettere ordinarie. La nostra implementazione conforme ha trovato zero opportunità in una frase thai di 23 caratteri, ed esattamente tre dopo aver inserito tre spazi a larghezza zero.
Articoli che potrebbero interessarti
Tutte le guide →Strumenti correlati
Fonti
- Unicode Consortium — Unicode Standard Annex #14: Unicode Line Breaking Algorithm — line break classes and rules LB1–LB31
- Unicode Consortium — LineBreak.txt in the Unicode Character Database — the authoritative class for every code point (17.0.0, 2025-07-29)
- W3C — CSS Text Module Level 3 — white-space, word-break, line-break, hyphens and overflow-wrap
- IETF (RFC Editor) — RFC 5322 — Internet Message Format, section 2.1.1 Line Length Limits (998 characters MUST, 78 SHOULD)
- IETF (RFC Editor) — RFC 3676 — The Text/Plain Format and DelSp Parameters (format=flowed)
Hai notato un errore in questo articolo?