Vai al contenuto
Allin

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

Daniel OkonkwoSviluppatore front-end e redattore Tech presso Allin

Performance web · Formati di file

Verificato su 5 fonti

Vedi il profilo
In breve

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.

Classi di interruzione consultate in LineBreak-17.0.0.txt (Unicode 17.0.0, datato 29/07/2025) e opportunità di interruzione prodotte da un'implementazione UAX #14 in Node 26.3.0. «Interruzione dopo?» descrive il comportamento nel testo corrente; l'algoritmo completo ha una quarantina di regole numerate e alcune dipendono dalla classe vicina.
CaratterePunto di codiceClasseInterruzione dopo?Visibile?
SpazioU+0020SPSì, come larghezza vuota
Spazio unificatoreU+00A0GLNoSì, come larghezza vuota
Spazio stretto unificatoreU+202FGLNoSì, più stretto
Trattino-menoU+002DHYSempre
Trattino unificatoreU+2011GLNoSempre
Trattino morbidoU+00ADBASolo se l'interruzione viene usata
Spazio a larghezza zeroU+200BZWMai
Unificatore di paroleU+2060WJNo, e nemmeno primaMai
Barra in un URLU+002FSYSì dopo, mai prima (LB13)Sempre
Ideogramma CJKU+4E00 e altri 172 560IDSì, fra quasi ogni coppiaSempre
Lettera thaiU+0E01SASolo con analisi da dizionarioSempre
A capo automatico del testoManda a capo le righe lunghe a una larghezza massima senza spezzare le parole.Prova lo strumento

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
GuidaConvertire fra formati di elenco senza perdere dati: le regole di virgolettatura che nessuno leggeTrasformare un elenco a capo in un elenco a virgole è banale finché un elemento non contiene una virgola. Le regole di virgolettatura della RFC 4180, perché un campo CSV può contenere un a capo, perché i fogli di calcolo europei usano il punto e virgola, e cosa fa un elemento vuoto all'andata e ritorno — ogni caso eseguito e stampato.SpiegazioneFrequenza delle parole e legge di Zipf: abbiamo contato sei libri in sei lingue e stimato la pendenzaLa parola di rango n compare circa 1/n volte rispetto alla prima. Abbiamo contato sei libri di pubblico dominio, stampato rango × frequenza, stimato log frequenza contro log rango e ottenuto pendenze fra -1,02 e -1,08 in tutte e sei le lingue — più i due punti in cui la legge si rompe.SpiegazioneContare le parole è ambiguo, e ogni strumento risponde in modo diversoUn conteggio di parole è una definizione, non una misura. Abbiamo contato lo stesso paragrafo in quattro modi ottenendo 25, 28, 33 e 38; poi abbiamo contato 50.000 caratteri di prosa ordinaria ottenendo accordo entro il 4,5 %. Lo scarto dipende interamente da composti, cifre e URL.SpiegazioneChe cosa misurano davvero gli indici di leggibilità (e le tre cose che non vedono)Il Flesch Reading Ease e il livello Flesch-Kincaid contano sillabe e lunghezza delle frasi. Nient'altro. Entrambe le formule per intero, un brano valutato da cima a fondo e il trucco della virgola che regala 3,9 anni scolastici senza cambiare una parola.SpiegazioneLa densità di parole chiave è una metrica morta, ed ecco che cosa l'ha sostituitaLa densità contava le occorrenze perché il recupero contava le occorrenze. TF-IDF, poi BM25 con la sua curva di saturazione, poi gli embedding l'hanno sostituita. Ecco la stessa pagina da 800 parole valutata in tre modi, e perché i tre non concordano.TutorialFiltrare righe secondo uno schema, senza riga di comandoQuesto è grep per chi non usa grep, con una differenza importante: la ricerca è una sottostringa letterale, quindi una vera espressione regolare restituisce un riquadro vuoto e nessun errore. Ogni affermazione è stata verificata eseguendo lo strumento.

Strumenti correlati

Fonti

Hai notato un errore in questo articolo?