Où une ligne peut se couper : l'algorithme Unicode derrière chaque paragraphe justifié
Publié le 01/07/2025 · 14 min de lecture · Outils texte & langage
Daniel Okonkwo — Développeur front-end et rédacteur Tech chez Allin
Performance web · Formats de fichiers
Vérifié à partir de 5 sources
Couper du texte en découpant sur les espaces est faux dans la plupart des systèmes d'écriture, et Unicode spécifie la bonne procédure : UAX #14, l'algorithme de coupure de ligne. Il attribue à chaque point de code une classe de coupure puis applique des règles numérotées à chaque paire de classes. Une espace insécable U+00A0 et une espace fine insécable U+202F sont de classe GL, colle, et toute coupure après elles est interdite : une implémentation conforme n'a trouvé aucune possibilité de coupure dans cost·100·USD écrit avec des espaces insécables, là où la version ordinaire en a deux. Un trait d'union conditionnel U+00AD est de classe BA : la coupure y est permise et le trait ne devient visible que si elle est prise. Une espace sans chasse U+200B est de classe ZW : elle permet la coupure et n'affiche rien, ce qui est la façon de couper un texte sans aucune espace. Un trait d'union insécable U+2011 est de la colle, tandis que le trait d'union-moins ordinaire U+002D est de classe HY et autorise bien une coupure après lui. Le japonais n'a pas besoin d'espaces : chaque idéogramme est de classe ID et la coupure est permise entre presque tous. Le thaï est de classe SA et n'en a aucune sans analyse par dictionnaire.
« Couper aux espaces » échoue dans la plupart des systèmes d'écriture. UAX #14 donne à chaque caractère une classe de coupure ; nous avons cherché les nôtres dans Unicode 17.0.0 et exécuté une implémentation conforme sur espaces insécables, traits d'union conditionnels, espaces de largeur nulle, URL, japonais et thaï.
« Couper aux espaces » décrit une seule famille d'écritures
Découpe sur l'espace et une phrase japonaise revient en un seul jeton : 日本語のテキストは、空白で区切らない ne contient aucune espace, pas plus que son équivalent thaï. Les deux se coupent pourtant très bien à l'écran, parce que la règle ne parle pas d'espaces. Passe une implémentation conforme sur cette phrase japonaise : elle signale seize possibilités de coupure pour dix-huit caractères — une entre presque chaque paire — tout en refusant de couper avant la virgule idéographique ou le point. La même implémentation sur la phrase thaïe en signale zéro, pour une raison sur laquelle nous revenons plus bas.
Le cas de l'écriture latine n'est pas sûr non plus. Écrit avec des espaces ordinaires, cost 100 USD offre deux possibilités de coupure ; écrit avec des espaces insécables U+00A0, il n'en offre aucune, et un couperet sur les espaces le rend comme un seul long jeton qu'il ne peut pas couper. Les caractères se ressemblent à l'identique. La différence vit dans les points de code, et tout outil de coupure qui ne lit que les formes produira une ligne trop longue ou une coupure typographiquement fautive.
UAX #14 : une classe par caractère, puis des règles sur les paires
La base de données de caractères Unicode contient un fichier LineBreak.txt qui attribue une classe à deux lettres à chaque point de code. Nous avons analysé la version actuelle — LineBreak-17.0.0.txt, daté du 29 juillet 2025 — et elle définit 49 classes distinctes réparties sur 3 654 plages. La plus grande de loin est ID, idéographique, avec 172 561 points de code ; AL, alphabétique ordinaire, en couvre 26 954 ; les intéressantes sont minuscules, GL avec 41 points de code, QU avec 39, WJ avec 2 et ZW avec exactement 1. L'algorithme parcourt ensuite la chaîne et consulte une règle pour chaque paire de classes adjacentes.
Les règles sont numérotées et réparties en deux groupes. Une poignée sont non modifiables : une implémentation ne peut pas les changer et prétendre à la conformité. Les autres, à partir de LB12a, sont un défaut raisonnable qu'une implémentation a le droit d'améliorer. LB12 interdit la coupure après de la colle. LB13 l'interdit avant le crochet fermant, le point d'exclamation, la barre oblique et la classe de ponctuation fermante, même après des espaces. LB18 autorise la coupure après une espace. LB21 l'interdit avant un trait d'union ou un caractère à coupure après. En cas de conflit, la règle de plus petit numéro l'emporte, et c'est pourquoi la spécification est une liste et non un tableau.
Cinq caractères invisibles qui décident où tombent les lignes
L'espace insécable et l'espace fine insécable sont toutes deux de classe GL. Elles occupent de la largeur et interdisent la coupure, ce qui garde un nombre avec son unité et un titre avec son nom. Le gluon de mots U+2060 est de classe WJ et va plus loin : pas de coupure avant ni après, et aucune largeur. L'espace sans chasse U+200B est l'inverse : classe ZW, aucune largeur, coupure permise. Et le trait d'union conditionnel U+00AD est de classe BA, un caractère à coupure après qui ne rend rien tant que la coupure n'est pas prise, moment où le moteur de rendu fournit un trait d'union.
L'essai sur le trait d'union conditionnel rend le comportement concret. Nous avons pris un composé allemand de 63 caractères et inséré cinq traits d'union conditionnels : longueur stockée 68, longueur visible 63. À la largeur 99, il s'affiche en un seul mot ininterrompu de 63 caractères. À la largeur 30, il tient sur trois lignes, à la largeur 20 sur quatre, et le trait d'union n'apparaît qu'en fin de ligne coupée — jamais au milieu. Retire les traits d'union conditionnels et le même mot à la largeur 20 revient en une seule ligne de 63 caractères qui déborde, parce que l'algorithme n'a nulle part le droit de couper.
La même astuce sauve les écritures que l'algorithme ne sait pas segmenter seul. Le thaï est de classe SA, dépendante d'un contexte complexe, et l'UAX #14 dit clairement que les suites de ces caractères exigent une analyse morphologique, qu'aucune possibilité de coupure ne sera trouvée sans elle, et qu'une implémentation qui en est dépourvue devrait les traiter comme des lettres ordinaires. Notre implémentation a signalé zéro possibilité dans une phrase thaïe de 23 caractères. En insérant trois espaces sans chasse aux frontières de mots, on en obtient exactement trois — invisibles dans le texte rendu, décisives pour la coupure.
Les URL se coupent à des endroits que tu n'as pas choisis
Donne une URL à l'algorithme : la réponse est plus intéressante que « nulle part » ou « partout ». Notre adresse test a produit sept possibilités de coupure : après la double barre oblique, après chaque barre simple, après chaque trait d'union du chemin, et après le point d'interrogation. Jamais avant une barre oblique — LB13 l'interdit catégoriquement, même après une espace — et jamais aux points du nom d'hôte, parce que le point est de classe IS et que LB15d refuse la coupure avant lui. Résultat : une URL coupée garde ses séparateurs en fin de ligne, là où un lecteur voit que la ligne continue.
Le danger est ce que tu fais de cette coupure. Une coupure douce est une décision d'affichage : la chaîne en mémoire ne change pas, et recomposer le paragraphe à une autre largeur déplace simplement la coupure. Une coupure dure insère un vrai caractère de fin de ligne dans les données. Nous avons coupé durement l'URL test à 24 colonnes ; en recollant les lignes sans rien, l'URL revient octet pour octet, mais en les recollant avec une espace — ce que feront un client de messagerie, une application de discussion ou un copier-coller distrait — on obtient quatre fragments séparés par des espaces, et le lien est mort. Ne coupe jamais durement ce qui doit rester un seul jeton.
La coupure dure détruit la recomposition — mesuré
Nous avons coupé un paragraphe à 32 colonnes, enregistré le résultat avec de vrais sauts de ligne, puis recoupé à 20 sans le décomposer d'abord. La sortie fait sept lignes, dont plusieurs bien plus courtes que 20 caractères, parce que chaque ligne dure a été recoupée isolément et que les fins irrégulières ne pouvaient pas être remplies. En décomposant d'abord — en recollant les lignes par une espace — puis en coupant à 20, on obtient cinq lignes pleines. Cet écart est le coût de la coupure dure : le texte ne sait plus quelles fins de ligne venaient de l'auteur et lesquelles du moteur précédent.
C'est aussi de là que vient la fameuse largeur fixe du courrier électronique, et le nombre que l'on cite n'est pas celui de la spécification. La RFC 5322 dit que chaque ligne NE DOIT PAS dépasser 998 caractères et NE DEVRAIT PAS dépasser 78, hors CRLF ; les 998 existent parce que des implémentations de transport refusent plus de 1 000 caractères par ligne, et les 78 parce que les logiciels d'affichage massacrent tout ce qui est plus long. La RFC 3676, qui définit format=flowed, reprend les 78 et explique que ce n'est ni 79 ni 80 parce que la dernière colonne est souvent réservée à un indicateur de coupure. Les 72 réellement utilisés par les clients de messagerie sont une convention par-dessus, qui laisse la place à plusieurs niveaux de marques de citation dans les réponses.
Format=flowed est le compromis qui a rendu le courrier à largeur fixe supportable : une ligne terminée par une espace avant son CRLF est une coupure douce que le lecteur peut recomposer, tandis qu'une ligne sans espace final est celle de l'auteur. C'est du texte ordinaire pour un client qui n'en a jamais entendu parler, et du texte recomposable pour un client qui connaît. La leçon générale dépasse le courrier : stocke le paragraphe, pas les lignes, et laisse le dernier moteur de rendu décider où tombent les coupures.
Ce que CSS permet de changer, et ce qu'il ne change pas
CSS Text Level 3 expose l'algorithme par cinq propriétés, et il vaut la peine d'en connaître les valeurs exactes. white-space accepte normal, pre, nowrap, pre-wrap, break-spaces ou pre-line, et vaut normal par défaut. word-break accepte normal, keep-all, break-all ou break-word. line-break accepte auto, loose, normal, strict ou anywhere, et règle la sévérité d'application de l'algorithme à la ponctuation est-asiatique. hyphens accepte none, manual ou auto, avec manual par défaut. overflow-wrap accepte normal, break-word ou anywhere. La spécification cite UAX #14 nommément pour les classes sous-jacentes.
Deux détails de cette liste sont faciles à rater. Comme hyphens vaut manual par défaut, les traits d'union conditionnels déjà présents dans ton texte sont honorés sans aucun CSS — et hyphens: none les supprime, mais la spécification dit explicitement que cela ne supprime pas les possibilités de coupure offertes par des caractères visibles comme U+002D et U+2010. Et overflow-wrap: anywhere est un dernier recours, pas un correctif : il autorise la coupure à n'importe quel caractère quand un mot déborderait, ce qui, pour le composé de 63 caractères ci-dessus, signifie une coupure au milieu d'un morphème là où un trait d'union conditionnel en aurait donné une correcte.
Les espaces typographiques ne sont pas de la décoration
La ponctuation française est la démonstration la plus nette que ces points de code font un vrai travail. Nous avons coupé deux fois la même phrase française : une fois avec des espaces ordinaires partout, une fois avec les espaces fines insécables correctes devant la ponctuation haute et une espace insécable devant l'unité. La première version offrait quatorze possibilités de coupure, dont une entre un nombre et son groupe de milliers et une avant un guillemet fermant. La seconde en offrait neuf, dont aucune ne séparait un signe du mot auquel il appartient. Même texte visible, ensemble différent de coupures légales.
Le même raisonnement dépasse largement le français. Un nombre et son unité, une abréviation et le nom qui suit, un chiffre et un signe pourcent, un ordinal et son nom : tous se lisent mal si une coupure tombe entre eux, et tous se règlent par un caractère invisible plutôt que par une règle dans l'outil de coupure. Notre outil de mise à la ligne laisse ces caractères intacts, seul comportement correct : les nettoyer, comme le fait un normaliseur d'espaces naïf, donne silencieusement au moteur de rendu la permission de couper là où l'auteur l'interdisait.
| Caractère | Point de code | Classe | Coupure après ? | Visible ? |
|---|---|---|---|---|
| Espace | U+0020 | SP | Oui | Oui, comme largeur blanche |
| Espace insécable | U+00A0 | GL | Non | Oui, comme largeur blanche |
| Espace fine insécable | U+202F | GL | Non | Oui, plus étroite |
| Trait d'union-moins | U+002D | HY | Oui | Toujours |
| Trait d'union insécable | U+2011 | GL | Non | Toujours |
| Trait d'union conditionnel | U+00AD | BA | Oui | Seulement si la coupure a lieu |
| Espace sans chasse | U+200B | ZW | Oui | Jamais |
| Gluon de mots | U+2060 | WJ | Non, ni avant lui | Jamais |
| Barre oblique dans une URL | U+002F | SY | Oui après, jamais avant (LB13) | Toujours |
| Idéogramme CJC | U+4E00 et 172 560 autres | ID | Oui, entre presque tous | Toujours |
| Lettre thaïe | U+0E01 | SA | Seulement avec analyse par dictionnaire | Toujours |
Questions fréquentes
- Pourquoi mon texte se coupe-t-il au milieu d'un nombre ?
- Parce que le séparateur de groupes est une espace ordinaire, classe SP, et que la règle LB18 autorise la coupure après toute espace. Remplace-la par une espace insécable U+00A0 ou une espace fine insécable U+202F, toutes deux de classe GL, et la possibilité disparaît : notre phrase française est passée de quatorze possibilités de coupure à neuf avec les espaces typographiques, et aucune des neuf restantes ne tombait dans un nombre.
- Quelle est la différence entre un trait d'union conditionnel et une espace sans chasse ?
- Les deux permettent la coupure et aucun n'est visible tant qu'il n'est pas utilisé, mais le trait d'union conditionnel U+00AD laisse un trait d'union quand la coupure a lieu, et l'espace sans chasse U+200B ne laisse rien. Utilise le trait d'union conditionnel dans un mot, là où le trait d'union est la bonne typographie ; utilise l'espace sans chasse entre des unités qui ne doivent pas gagner de trait d'union, comme les parties d'un long identifiant ou d'une URL, et dans les écritures qui ne coupent jamais avec un trait d'union.
- Faut-il couper durement le texte à 72 ou 80 caractères ?
- Seulement si un protocole l'exige. La RFC 5322 fixe une limite dure de 998 caractères par ligne et une recommandation de 78 ; les 72 utilisés par les clients de messagerie laissent la place aux marques de citation dans les réponses et sont une convention, pas une spécification. La coupure dure coûte la recomposition : recouper à 20 un paragraphe déjà coupé durement à 32 colonnes a donné sept lignes irrégulières, contre cinq lignes pleines en le décomposant d'abord.
- Comment empêcher une longue URL de déborder de son conteneur ?
- Pas en insérant des sauts de ligne. L'algorithme propose déjà des coupures après la double barre oblique, après chaque barre et après chaque trait d'union — sept possibilités dans notre adresse test — donc une coupure douce suffit d'ordinaire. Si le conteneur est plus étroit que la plus longue suite insécable, ajoute overflow-wrap: anywhere pour cet élément ou insère des espaces sans chasse, qui ne coûtent rien à la copie. Une URL coupée durement puis recollée avec une espace n'est plus une URL.
- L'algorithme sait-il où finissent les mots thaïs ?
- Non, et l'UAX #14 le dit. Les caractères thaïs, lao et khmers sont de classe SA, dépendante d'un contexte complexe, et l'annexe précise que leurs suites exigent une analyse morphologique, qu'aucune possibilité de coupure ne sera trouvée sans elle, et qu'une implémentation qui en manque devrait les traiter comme des lettres ordinaires. Notre implémentation conforme a trouvé zéro possibilité dans une phrase thaïe de 23 caractères, et exactement trois une fois trois espaces sans chasse insérées.
Articles qui pourraient t'intéresser
Tous les guides →Outils similaires
Sources
- 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)
Tu as repéré une erreur dans cet article ?