# Césure et justification

> Comment Postext coupe les lignes, justifie le texte et règle l’espacement des mots avec l’algorithme de Knuth-Plass

- Version HTML: https://postext.dev/fr/docs/justification
- Dernière mise à jour: 2026-09-29
- Temps de lecture: 14 min
- Autres langues: [en](https://postext.dev/en/docs/justification.md), [es](https://postext.dev/es/docs/justification.md), [ca](https://postext.dev/ca/docs/justification.md), [pt](https://postext.dev/pt/docs/justification.md), [zh](https://postext.dev/zh/docs/justification.md), [ja](https://postext.dev/ja/docs/justification.md), [ar](https://postext.dev/ar/docs/justification.md)

## En bref

Cette page explique comment Postext répartit les mots le long de chaque ligne. Quand les deux bords d’un paragraphe sont droits, les blancs entre les mots peuvent devenir trop larges et faire mauvais effet. Postext examine le paragraphe entier avant de décider où chaque ligne se termine, si bien que les blancs restent réguliers. Il peut aussi couper les mots longs avec un trait d’union au bon endroit. Vous pouvez fixer jusqu’où les blancs peuvent s’élargir et repérer les lignes qui restent lâches. La page montre aussi comment Postext fait finir les colonnes à la même hauteur.

**La différence entre une composition d’amateur et une composition professionnelle se joue dans les espaces entre les mots.**

Ouvrez n’importe quel roman de poche. Le texte est justifié : les deux bords de chaque paragraphe sont parfaitement alignés. Mais regardez de plus près. Les espaces entre les mots sont presque uniformes, ligne après ligne. Pas de lézardes de blanc qui descendent le long de la page. Pas de lignes où deux mots restent isolés, séparés par un immense vide. Obtenir ce résultat est bien plus difficile qu’il n’y paraît, et c’est le problème qui a absorbé plus d’efforts d’ingénierie typographique que tout autre.

Postext le résout avec l’algorithme qu’utilise TeX depuis 1981 : la **coupure optimale des lignes de Knuth-Plass**. Associé à des motifs de césure de la qualité de TeX, à des limites d’espacement des mots configurables et à un système visuel de débogage qui repère les lignes problématiques, le moteur produit un texte justifié conforme aux exigences de l’édition.

## Le problème

Quand un texte est composé avec `textAlign: 'justify'`, chaque ligne sauf la dernière doit être étirée ou comprimée pour remplir exactement la largeur de la colonne. Le moteur répartit la différence entre la largeur naturelle du contenu et la largeur de la colonne sur les espaces entre les mots de la ligne. (La dernière ligne reste en drapeau à sa largeur naturelle, avec une exception traitée dans [La dernière ligne](https://postext.dev/fr/docs/justification.md#la-dernière-ligne).)

Si une ligne compte beaucoup de mots, chaque espace absorbe un ajustement minime, invisible pour le lecteur. Mais si une ligne compte peu de mots (parce qu’un mot long a imposé une coupure précoce), chaque espace doit s’étirer considérablement. On obtient alors une **ligne lâche** : une ligne dont l’espacement des mots est si large qu’il casse le rythme de lecture et crée de vilains trous.

Le problème inverse existe aussi. Si le moteur fait tenir trop de mots sur une ligne, les espaces rétrécissent sous leur largeur naturelle et produisent une **ligne serrée**, où les mots paraissent tassés.

Un algorithme naïf de coupure des lignes, comme celui qu’emploie CSS, décide une ligne à la fois. Il remplit la ligne en cours avec autant de mots que possible, coupe et passe à la suivante. Cette méthode **gloutonne** (*first-fit*) a une faiblesse fondamentale : elle ne voit pas l’avenir. Une décision qui paraît optimale pour la ligne 5 peut imposer à la ligne 6 une coupure désastreuse. Quand l’algorithme arrive à la ligne 6, il est trop tard : la ligne 5 est déjà arrêtée.

## Knuth-Plass : voir le paragraphe entier

L’algorithme de Knuth-Plass, publié par Donald Knuth et Michael Plass en 1981, procède de façon radicalement différente. Au lieu de couper une ligne à la fois, il envisage **toutes les façons possibles de couper le paragraphe entier** et retient la combinaison qui minimise la « laideur » (*badness*) totale sur l’ensemble des lignes. C’est l’algorithme de TeX, et c’est la raison pour laquelle les documents composés avec TeX font référence en matière de texte justifié depuis plus de quarante ans.

### Le modèle boîte-colle-pénalité

Knuth-Plass ne raisonne pas en mots et en espaces. Il modélise le texte comme une suite de trois primitives :

| Primitive | Représente | Comportement |
| --- | --- | --- |
| **Boîte** | Un mot ou un fragment de texte | A une largeur fixe. Ne peut être ni étirée ni comprimée. Ne peut pas être coupée. |
| **Colle** | Espace entre les mots | A une largeur *naturelle*, une capacité d’*étirement* et une capacité de *rétrécissement*. Le moteur peut ajuster la colle dans ces limites pour remplir la ligne. |
| **Pénalité** | Un point de coupure possible | A un coût. Pénalité faible = coupure bon marché. Pénalité élevée = coupure coûteuse. Une pénalité *marquée* signale un point de césure (elle ajoute un trait d’union visible si elle est utilisée). |

Un paragraphe devient une suite comme celle-ci :

```
[box "The"] [glue] [box "quick"] [glue] [box "brown"] [glue] [box "fox"]
[penalty -∞]  ← forced break at paragraph end
```

Quand la césure est active, les mots longs sont découpés en fragments séparés par des pénalités :

```
[box "ty"] [penalty 50, flagged] [box "pog"] [penalty 50, flagged] [box "raphy"]
```

Chaque pénalité marquée coûte 50 : ce n’est pas gratuit, mais c’est moins cher qu’une ligne lâche.

> **Figure: Primitives boîte-colle-pénalité**
> Légende visuelle des trois primitives : la boîte pour un fragment de mot, la colle pour l’espace entre les mots et la pénalité pour un point de coupure possible. Le paragraphe d’exemple montre comment un point de césure devient une pénalité marquée.
>
> *Chaque paragraphe devient une suite de boîtes, de colles et de pénalités.*

### Comment il trouve l’optimum

L’algorithme repose sur la programmation dynamique. Il tient à jour un ensemble de **nœuds actifs** (des points de coupure potentiels qui pourraient ouvrir de nouvelles lignes) et évalue chaque coupure réalisable à partir de chaque nœud actif. Pour chaque coupure candidate, il calcule :

1. **Le taux d’ajustement (r)** : de combien la colle de cette ligne doit s’étirer ou rétrécir. `r = 0` signifie que la ligne tombe juste. `r > 0` signifie un étirement (ligne lâche). `r < 0` signifie un rétrécissement (ligne serrée).

2. **La laideur** (*badness*) : une mesure de l’irrégularité de l’espacement, calculée comme `100 × |r|³`. Cette croissance cubique fait qu’une ligne légèrement lâche reste tolérable, alors qu’une ligne très lâche est lourdement pénalisée. Une ligne à `r = 2` a une laideur de 800 ; une ligne à `r = 0.5` a une laideur de 12.

3. **La classe d’ajustement** : chaque ligne est classée serrée (`r < -0.5`), normale (`-0.5 ≤ r < 0.5`), lâche (`0.5 ≤ r < 1.0`) ou très lâche (`r ≥ 1.0`). Une ligne serrée à côté d’une ligne très lâche produit un contraste choquant ; les lignes voisines dont les classes sont distantes de plus d’un cran sont donc pénalisées.

4. **Les démérites** : le coût total d’une coupure à cet endroit. Comme dans TeX, la laideur et la pénalité du point de coupure se combinent dans une formule au carré : `demerits = (1 + badness + penalty)²` quand la pénalité est positive ou nulle ; une pénalité négative (une coupure *souhaitable*) soustrait au contraire son carré : `(1 + badness)² − penalty²`. Deux démérites fixes s’y ajoutent ensuite :
   - **Démérite de traits d’union consécutifs** (3000 par défaut) : pénalise deux lignes de suite terminées par une césure, car les traits d’union empilés gênent l’œil.
   - **Démérite de classe d’ajustement** (100 par défaut) : ajouté quand la classe d’ajustement de cette ligne est distante de plus d’un cran de celle de la ligne précédente.

   La pénalité de fin trop courte décrite plus bas entre aussi dans cette formule, injectée sous forme de laideur supplémentaire ; les pénalités d’orpheline et de veuve interviennent à un stade ultérieur, quand un paragraphe est coupé entre deux colonnes.

L’algorithme retient la suite de coupures dont le total des démérites est le plus faible sur l’ensemble du paragraphe : tout son avantage sur la méthode gloutonne est là.

> **Figure: La laideur en fonction du taux d’ajustement**
> La laideur croît comme 100 fois la valeur absolue de r au cube. Les petits étirements ou rétrécissements coûtent peu, mais les étirements extrêmes deviennent extrêmement coûteux, et c’est pourquoi l’algorithme les évite.
>
> *La laideur croît de façon cubique : un léger déséquilibre est tolérable, un fort déséquilibre est durement sanctionné.*

> **Figure: Classes d’ajustement**
> Chaque ligne est classée serrée, normale, lâche ou très lâche selon son taux d’ajustement. Les lignes voisines dont les classes diffèrent de plus d’un cran sont pénalisées.
>
> *Les lignes voisines distantes de plus d’une classe reçoivent le démérite d’ajustement.*

L’algorithme remonte ensuite les nœuds actifs pour retrouver le chemin dont le total des démérites est le plus faible : l’ensemble de points de coupure globalement optimal pour tout le paragraphe.

#### Pénalités d’orpheline, de veuve et de fin trop courte

Postext étend le modèle de coût standard avec trois pénalités éditoriales qui détournent le moteur des mises en page dont les fins de paragraphe et de colonne sont visuellement médiocres :

- La **pénalité d’orpheline** s’applique quand une coupure candidate laisserait moins de `orphanMinLines` lignes en haut de la colonne suivante. `orphanPenalty` vaut `1000` par défaut.
- La **pénalité de veuve** s’applique quand la coupure laisserait moins de `widowMinLines` lignes en bas de la colonne en cours. `widowPenalty` vaut `1000` par défaut.
- La **pénalité de fin trop courte** s’applique quand la dernière ligne du paragraphe serait plus courte qu’environ `runtMinCharacters × normalSpaceWidth` pixels, soit un nombre d’espaces-mots et non de lettres : la valeur par défaut de 20 correspond à une dernière ligne d’environ 8 à 12 lettres. `runtPenalty` vaut `1000` par défaut, la même pour toute fin trop courte ; avec `gradedRuntPenalty`, elle est proportionnelle à ce qui manque à la ligne, `runtPenalty × (1 − width / threshold)`, si bien qu’une fin de deux mots coûte moins qu’une fin d’un seul mot et l’emporte quand une ligne au-dessus peut céder un mot. Contrairement aux pénalités d’orpheline et de veuve (qui s’ajoutent linéairement au démérite de la coupure), la pénalité de fin trop courte est injectée comme une *laideur* équivalente dans la formule au carré de Knuth–Plass, pour rivaliser à la même échelle que la laideur des lignes (qui plafonne à 10000) au lieu d’être écrasée par elle.

Quand la pénalité perd malgré tout (toutes les autres coupures sont irréalisables), le moteur se rabat sur ce que fait un compositeur à la main : il compose le paragraphe avec une ligne de moins (`tightenRunts`). Les espaces entre les mots de chaque ligne se resserrent, jamais au-delà de `minWordSpacing`, et quand cela ne suffit pas à remonter les mots isolés, un peu d’approche négative s’y ajoute, le plus petit pas qui fonctionne et jamais plus de `maxRuntTracking` millièmes de cadratin. La composition plus courte doit aussi respecter `maxWordSpacing`. Faire tenir les mots sur une ligne de moins resserre la plupart des lignes, mais peut déplacer une coupure de sorte qu’une autre ligne doive s’étirer beaucoup. Une composition qui étirerait une ligne au-delà de `maxWordSpacing` ou, quand le paragraphe a déjà une ligne plus lâche, au-delà de cette ligne, n’est pas une correction, et la fin trop courte demeure. Seules comptent les lignes qui restent justifiées : une ligne que le paragraphe compose en drapeau parce qu’elle s’étirerait au-delà de 3× l’espace normal (voir [Lignes que le moteur de coupure ne peut pas remplir](https://postext.dev/fr/docs/justification.md#lignes-que-le-moteur-de-coupure-ne-peut-pas-remplir)) ne relève pas la barre, et la composition plus courte ne peut pas mettre en drapeau plus de lignes que le paragraphe n’en avait. La correction n’échange pas non plus une ligne en drapeau contre des lignes justifiées au-delà de cette barre : le paragraphe garde sa ligne en drapeau et sa fin trop courte. L’équilibrage des colonnes suit la même règle dans l’autre sens : un paragraphe qu’il allongerait d’une ligne pour remplir une colonne courte est laissé tel quel quand cette ligne supplémentaire finirait trop courte ; remplir une colonne ne justifie pas d’isoler une syllabe.

Un bloc composé plus court de cette façon le signale : `VDTBlock.runtFix` contient `{ tracking }`, l’approche négative utilisée en millièmes de cadratin, 0 quand les espaces entre les mots ont suffi. Aucun moteur de rendu ne le lit ; il sert à qui ajuste le texte, car un paragraphe au plafond paraît serré à côté de ses voisins, et quelques mots de moins l’auraient composé sans approche.

La pénalité de fin trop courte s’ajoute aux démérites du nœud candidat à l’intérieur du moteur de coupure, si bien que le solveur peut échanger une ligne un peu plus lâche contre une dernière ligne plus longue, mais seulement dans la limite de `maxWordSpacing` : une ligne étirée au-delà de cette limite porte une laideur supplémentaire plus grande que n’importe quelle pénalité de fin trop courte ou de césure, si bien que le moteur de coupure préfère une dernière ligne courte, ou un dernier mot coupé, à un espacement des mots qui dépasse la limite. Les pénalités d’orpheline et de veuve entrent dans une optimisation distincte qui s’exécute quand un paragraphe franchit une limite de colonne : chaque coupure candidate est notée (démérites de vide, d’orpheline et de veuve additionnés) et la moins coûteuse l’emporte, de sorte que le moteur préfère naturellement les coupures qui évitent les deux chaque fois que c’est possible. Réglez le compromis en ajustant `orphanPenalty`, `widowPenalty` ou `runtPenalty` ; mettez l’une d’elles à `0` pour désactiver entièrement la règle correspondante. Les éléments de liste s’y soumettent via `avoidOrphansInLists`, `avoidWidowsInLists` et `avoidRuntsInLists` (tous à `true` par défaut). Ces règles gardent ensemble les lignes d’un même élément ; `shortListSplit` garde ensemble les éléments d’une liste courte (désactivé par défaut, voir [Texte courant](https://postext.dev/fr/docs/configuration-text.md#texte-courant)).

### Pourquoi c’est important

> **Figure: Méthode gloutonne contre Knuth-Plass optimal**
> Illustration côte à côte : l’algorithme glouton prend la première ligne qui tient et compromet les lignes suivantes, ce qui donne des paragraphes à l’espacement irrégulier. Knuth-Plass évalue le paragraphe entier de façon globale et produit des lignes régulières.
>
> *Une décision gloutonne et locale perd là où un plan globalement optimal gagne.*

La différence se voit en pratique. Dans une mise en page gloutonne, vous trouverez des paragraphes où une ligne est nettement plus lâche que ses voisines, et en regardant bien, vous verrez que c’est parce que la ligne *précédente* a pris un mot de trop. Knuth-Plass évite cela en acceptant une ligne courante un peu moins bonne en échange d’une ligne suivante bien meilleure, parce qu’il voit les conséquences.

### L’implémentation de Postext

Postext implémente l’algorithme de Knuth-Plass complet dans le module `knuthPlass/` du paquet principal : un noyau de programmation dynamique (nœuds actifs, démérites, remontée) plus deux chemins d’adaptation qui convertissent le texte dans le modèle boîte-colle-pénalité :

- **Chemin du texte brut** : utilise `@chenglou/pretext` pour mesurer le texte sans DOM. Pretext fournit les largeurs des segments et des traits d’union conditionnels ; Postext les convertit en éléments KP.
- **Chemin du texte enrichi** : gère le gras et l’italique avec une mesure fondée sur Canvas. Chaque jeton stylé devient une ou plusieurs boîtes, et les points de césure sont insérés sous forme de pénalités.

Les deux chemins calculent pour chaque ligne un `justifiedSpaceRatio` : la largeur à laquelle chaque espace de la ligne est peinte, divisée par l’espace du style romain. Toutes les espaces d’une ligne sont peintes de la même façon, quel que soit le style dont chacune provient. Les limites encadrent quelque chose d’un peu différent, l’écart de chaque espace par rapport à sa propre largeur, compté en espaces romaines, et une espace italique ou grasse est plus étroite ou plus large que l’espace romaine. Une ligne qui en contient une est donc peinte un peu en dessous de `minWordSpacing` ou au-dessus de `maxWordSpacing` alors qu’elle a été coupée dans ces limites, et elle porte aussi `justifiedSpaceLimitRatio`, le nombre que les limites encadrent : 1 plus le jeu de la ligne rapporté à ses espaces, chacune à sa propre largeur. `lineWordSpacing(line)` le renvoie, ou `justifiedSpaceRatio` sur une ligne dont toutes les espaces sont romaines, où les deux nombres se confondent. Il alimente le système de débogage des lignes lâches décrit plus bas ; la mise en page elle-même (le repli en drapeau, la correction des fins trop courtes, le levier des paragraphes lâches) continue de lire le taux peint. Ces taux ne sont calculés que pour les lignes autres que la dernière ; la composition de la dernière ligne est traitée dans [La dernière ligne](https://postext.dev/fr/docs/justification.md#la-dernière-ligne).

Autour du moteur de coupure, la mesure du texte se trouve dans le module `measure/` (les chemins de mesure du texte brut et enrichi, les métriques des glyphes Canvas et la gestion des chaînes de police), derrière un cache de mesure explicite. Quand les styles d’une famille arrivent ou disparaissent, le moteur jette ce qu’il a mesuré dans cette famille, dans ses caches de largeur et dans le cache de mesure que tient un hôte, si bien que les mesures restent exactes une fois les polices web chargées, sans que l’hôte ait rien à vider (voir [Charger les polices avant la mise en page](https://postext.dev/fr/docs/configuration-programmatic-usage.md#charger-les-polices-avant-la-mise-en-page)). L’analyse, l’étape qui transforme le texte source en blocs et en segments en ligne, se trouve dans le module `parse/`. Voir la page [Architecture](https://postext.dev/fr/docs/architecture.md) pour l’organisation de ces modules fichier par fichier.

La coupure des lignes interagit aussi avec les types de contenu plus récents. Les ressources flottantes occupent la bande supérieure ou inférieure d’une colonne ou d’une page, ce qui raccourcit les étendues de colonne auxquelles réagissent les pénalités d’orpheline, de veuve et de vide. Les légendes et les cellules de tableau sont mesurées comme des suites de texte enrichi avec retour à la ligne, dans les polices des légendes et des tableaux. Les lignes de formules centrées sont centrées et atomiques : jamais justifiées ni coupées. Voir [Format du document › Ressources](https://postext.dev/fr/docs/document-format.md#ressources) et [Configuration › Style des tableaux](https://postext.dev/fr/docs/configuration-resources.md#style-de-tableau) / [Style des légendes](https://postext.dev/fr/docs/configuration-resources.md#style-de-légende) pour le détail.

**Comportement de repli** : si Knuth-Plass ne produit aucune coupure valide (ce qui peut arriver avec des colonnes extrêmement étroites ou des mots très longs), le moteur se rabat sur la méthode gloutonne `layoutNextLine()` de Pretext. La mise en page aboutit ainsi toujours.

## Césure

La césure et la justification sont indissociables. Sans césure, le seul moyen pour le moteur d’éviter une ligne lâche est de renvoyer un mot à la ligne suivante, ce qui ne fait souvent que déplacer le problème. La césure offre au moteur un ensemble bien plus vaste de points de coupure à envisager, ce qui améliore considérablement la qualité du texte justifié.

> **Figure: Texte justifié avec et sans césure**
> La colonne de gauche n’a pas de césure : la largeur des lignes varie énormément, car le moteur ne peut déplacer que des mots entiers. La colonne de droite a la césure activée : les lignes sont presque uniformes et un mot est coupé par un trait d’union.
>
> *La césure réduit considérablement les écarts d’espacement dans un texte justifié.*

### Des motifs de la qualité de TeX

Postext utilise **Hypher** (`hypher` v0.2.5) pour la césure, avec les **motifs de césure TeX/Liang**. Ce sont les motifs qu’utilise TeX depuis 1983 : une représentation compacte des règles de frontière syllabique, tirée de grands corpus de mots par l’algorithme de génération de motifs de Frank Liang.

Les motifs codent un ensemble de règles numérotées qui, superposées à un mot, indiquent où les coupures sont permises (nombres impairs) et interdites (nombres pairs). Les paramètres `leftmin` et `rightmin` du fichier de motifs de chaque langue garantissent un nombre minimal de caractères avant et après tout point de coupure. Pour l’anglais (`en-us`), ils valent en général 2 et 3 : un mot doit garder au moins 2 caractères avant le trait d’union et 3 après.

### Langues prises en charge

| Code de langue | Langue |
| --- | --- |
| `'en-us'` | Anglais (États-Unis) |
| `'es'` | Espagnol |
| `'fr'` | Français |
| `'de'` | Allemand |
| `'it'` | Italien |
| `'pt'` | Portugais |
| `'ca'` | Catalan |
| `'nl'` | Néerlandais |

Chaque langue charge son propre jeu de motifs. Les instances d’Hypher sont créées à la demande et mises en cache : le premier appel pour une langue donnée paie le coût d’initialisation, les appels suivants sont instantanés. Une langue avec indication de région utilise les motifs de sa langue (`'es-ES'` se coupe avec `'es'`, `'en-GB'` avec `'en-us'`). Une langue sans motifs ici se rabat sur `en-us`, et le moteur émet une seule fois un avertissement dans la console (voir [Césure](https://postext.dev/fr/docs/configuration-text.md#césure) dans la référence de configuration). Le chinois, le japonais et le coréen font exception : ils sont composés sans césure et sans cet avertissement, et leurs lignes se coupent entre les caractères (voir [Chinois, japonais et coréen](https://postext.dev/fr/docs/justification.md#chinois-japonais-et-coréen) plus bas).

Le **catalan** suit les règles de l’Institut d’Estudis Catalans (*Llibre d’estil*, chapitre VI). C’est le cas depuis postext 1.14 :

- Une coupure laisse au moins deux lettres de chaque côté (`ter-ra`, `cai-xa`, `plu-ja`).
- L’*ela geminada* se coupe entre ses deux *l*, et le trait d’union prend la place du point médian : *il·lusió* devient `il-` | `lusió`. La ligne est mesurée sans le point, si bien que la justification et Knuth–Plass voient sa largeur réelle.
- Deux voyelles en hiatus restent ensemble (`cièn-cia`, `ca-mions`). Un *i* ou un *u* entre deux voyelles compte comme une consonne et ouvre sa syllabe (`fe-ia`, `ve-ient`).
- Aucune coupure ne suit une apostrophe (`s'ha-via`, jamais `s'-havia`).
- Les mots préfixés courants se coupent au préfixe (`vos-altres`, `cel-obert`, `ben-estar`).
- Pour garder entiers les mots suivis de pronoms enclitiques, sauf à leur propre trait d’union (`portar-` | `lo`), comme le préfère l’IEC, mettez [`hyphenation.compounds`](https://postext.dev/fr/docs/configuration-text.md#césure) à `false`.

### Pourquoi Hypher (et pas un algorithme maison)

Le système de césure d’origine de Postext reposait sur une **heuristique maison fondée sur les voyelles** : il repérait les frontières syllabiques en cherchant les groupes de voyelles, les préfixes courants (`over-`, `under-`, `inter-`) et les suffixes courants (`-tion`, `-ment`, `-sion`). Il était simple et rapide, mais fondamentalement limité :

| Aspect | Heuristique maison | Hypher (motifs TeX) |
| --- | --- | --- |
| **Exactitude** | Bonne pour les mots courants, peu fiable pour les mots rares. La détection des voyelles manque de nombreux points de coupure valides et en crée d’invalides. | Quasi parfaite. Les motifs sont générés à partir de grands corpus et affinés depuis plus de 40 ans. |
| **Couverture des langues** | Il fallait définir à la main les voyelles, les préfixes et les suffixes de chaque langue : un travail fastidieux et sujet aux erreurs. | Des fichiers de motifs existent pour plus de 50 langues, entretenus par la communauté TeX. Ajouter une langue revient à ajouter un import. |
| **Standard du secteur** | Aucun standard reconnu. Ni outils ni soutien d’une communauté. | Les mêmes motifs que TeX, LibreOffice, Firefox, Chrome et pratiquement tous les systèmes de composition professionnels. |
| **Maintenance** | Chaque cas limite est un bogue à corriger à la main. | Fichiers de motifs entretenus par la communauté. Les corrections viennent de l’amont. |
| **Taille du bundle** | Environ 170 lignes, sans dépendance. | Le cœur d’Hypher pèse environ 3 Ko. Chaque fichier de motifs de langue ajoute 20 à 80 Ko (5 à 20 Ko compressé en gzip). Les huit langues fournies ajoutent environ 300 Ko (environ 80 Ko en gzip). |
| **Performances** | Très rapide (simple parcours de chaîne). | Rapide (recherche dans un trie à chaque caractère). Négligeable en pratique : la césure n’est jamais le goulot d’étranglement. |

Le compromis est clair : un bundle plus lourd en échange d’une exactitude bien meilleure et d’aucune charge de maintenance. Pour un moteur de mise en page qui vise une qualité éditoriale, l’exactitude l’emporte. Une seule mauvaise césure dans un livre imprimé coûte plus cher que quelques kilo-octets de motifs en plus.

### Comment la césure s’intègre à Knuth-Plass

Avant que le texte entre dans l’algorithme de Knuth-Plass, le moteur le prétraite avec Hypher et insère des **traits d’union conditionnels** (Unicode `\u00AD`) à chaque point de coupure légitime. Ces caractères invisibles sont ensuite convertis en **pénalités** KP d’un coût de 50, avec `flagged: true`.

L’algorithme traite les coupures de césure comme une option de plus à évaluer, à côté des frontières naturelles entre les mots (où la colle permet une coupure). Si un trait d’union donne un total de démérites plus faible que de laisser la ligne lâche, l’algorithme prend le trait d’union. Sinon, il laisse le mot intact.

Le démérite des traits d’union consécutifs (3000 par défaut) fait que l’algorithme évite fortement de placer des traits d’union sur deux lignes voisines, une convention typographique que pratiquement tous les guides de style imposent.

Un trait d’union sur la dernière ligne d’une colonne ou d’une page envoie le lecteur à la colonne suivante au milieu d’un mot. `bodyText.hyphenateAcrossColumns: false` le tient à l’écart de ces lignes : quand la ligne qui ferme une colonne se termine sur un trait d’union, le paragraphe est recoupé avec, à cet endroit, un trait d’union coûtant autant qu’une fin trop courte, si bien que le moteur de coupure termine cette ligne sur un mot entier chaque fois que les espaces entre les mots des lignes au-dessus peuvent absorber la différence dans les limites de `maxWordSpacing` et `minWordSpacing`. Quand ils ne le peuvent pas, le trait d’union reste. Cela vaut pour le premier saut de colonne d’un paragraphe, et pour les suivants qui tombent là où une colonne pleine se termine. Un saut ultérieur qui tombe ailleurs (une colonne raccourcie pour en écarter une veuve, une bande de fin coupée à niveau) fait l’objet d’une seconde recoupe à partir de cette colonne : les lignes déjà composées dans les colonnes précédentes gardent leurs coupures, et seul le reste du paragraphe est recoupé. Un trait d’union ne reste donc que là où aucune composition dans les limites ne l’évite, en pratique au premier saut de colonne d’un paragraphe. `hyphenateAcrossColumns: 'withinPage'` garde la protection pour les lignes qui ferment une page : le lecteur qui tourne la page, ou passe à la page en regard, trouve un mot entier, tandis qu’un mot partagé entre deux colonnes de la même page est laissé tel quel. Sur trois colonnes, cela ne laisse que deux fins de colonne sur trois, si bien que moins de paragraphes sont recoupés et que moins de lignes prennent des espaces plus larges ou plus étroites pour le compenser. Le moteur de coupure ne distingue les façons d’atteindre une ligne que jusqu’à la dernière ligne qu’il protège, si bien qu’une nouvelle tentative coûte à peu près autant que la première mesure : une mesure de plus pour chaque paragraphe qu’il recoupe.

Par défaut, la césure ne s’applique que si `bodyText.hyphenation.enabled` vaut `true` **et** `bodyText.textAlign` vaut `'justify'` : un bord en drapeau est fait pour varier. Le texte en drapeau peut aussi recevoir des césures, avec `bodyText.hyphenation.ragged: true`. Une ligne en drapeau n’a pas d’espacement des mots à égaliser ; c’est donc une **zone de césure** qui décide : un mot qui ne tient pas n’est coupé que si le renvoyer entier à la ligne suivante laisserait un vide plus large que `hyphenation.zone` (3 em par défaut). Knuth-Plass compose aussi le texte courant en drapeau (`bodyText.optimalRagged`, activé par défaut) : ses espaces entre les mots gardent leur largeur, chaque ligne paie ce qui lui manque pour atteindre la pleine largeur, et la zone décide des syllabes qu’elle peut prendre ; deux fins de syllabe de suite coûtent le démérite des traits d’union consécutifs. Composé ligne par ligne (`optimalRagged: false`), le texte ne compte jamais plus de deux lignes de suite terminées sur une syllabe. Voir [Texte en drapeau](https://postext.dev/fr/docs/configuration-text.md#texte-en-drapeau).

Deux occasions de coupure existent quel que soit ce réglage. Un **trait d’union entre deux lettres**, `enseñanza-aprendizaje`, `físico-química`, est un endroit où la ligne peut se terminer : le mot porte déjà le trait d’union, on n’ajoute donc rien. Knuth-Plass le prend dans tout paragraphe avec `bodyText.breakAfterHyphens` (activé par défaut), au prix d’une syllabe ; dans un paragraphe justifié sans mise en forme en ligne, seulement avec deux lettres de chaque côté du trait d’union, si bien qu’aucune ligne ne se termine sur le `e-` de `e-mail`. Quand il est désactivé, comme jusqu’à postext 1.4, un paragraphe justifié sans mise en forme en ligne ne se coupe pas juste après un tel trait d’union, alors que le même paragraphe avec un seul mot en italique n’importe où le fait ; les livres enregistrés avant postext 1.5 le lisent désactivé. La ligne se termine sur le trait d’union du texte lui-même, que la ligne enregistre comme `hardHyphen` à côté de `hyphenated`, si bien qu’un lecteur qui supprime un trait d’union ajouté (les titres courants, le curseur du Sandbox) garde celui-ci. Un **tiret cadratin ou demi-cadratin collé entre deux mots**, `say—that’s`, `riddles.—I`, `Hamburg–Berlin`, est aussi un endroit où la ligne peut se terminer, avec les deux moteurs de coupure, grâce à `bodyText.breakAfterDashes` (activé par défaut) : la ligne se termine sur le tiret, rien n’est ajouté ni facturé. Il en va de même d’un tiret qui termine une suite d’un style avant un mot d’un autre style (`see—*and*`). La ligne est alors `hyphenated`, sans `hardHyphen`. Jamais après un tiret qui ouvre une incise ou une réplique de dialogue (`—dijo`, `said "—Hola` ; un guillemet qui ferme un mot peut précéder le tiret, comme dans `"no"—and` ou l’allemand `„nein“—und`), avant une ponctuation, avant un guillemet ou une parenthèse (`thinking—" and`, `says—“no”`), à l’intérieur d’une suite de tirets ou d’un intervalle de nombres (`1914–1918`). Enfin, un **mot plus large que toute sa ligne** (une cellule de tableau étroite, un long composé dans une colonne étroite) ne déborde jamais : le moteur le coupe à la dernière syllabe qui tient, avec un trait d’union, ou au dernier caractère qui tient quand le dictionnaire n’offre aucune syllabe à cet endroit ; une coupure voisine d’un trait d’union que porte le mot tombe après ce trait d’union et n’en ajoute aucun, si bien que le mot n’en montre jamais deux. Le reste du mot garde ses propres points de coupure : un composé continue de se couper à ses traits d’union et une adresse web à ses jointures, alors que jusqu’à postext 1.4 le reste était coupé aux syllabes du dictionnaire. Un paragraphe sans mise en forme en ligne est coupé au dernier caractère qui tient, sans trait d’union, sauf s’il reçoit des césures. Aucun ensemble de coupures autour d’un tel mot n’est réalisable pour Knuth-Plass ; un paragraphe justifié qui en contient un est donc composé ligne par ligne.

### Mots composés

Un composé joint par un trait d’union (`after-dinner`, `teórico-práctico`) peut se couper à deux sortes d’endroits : après son propre trait d’union, et aux syllabes du dictionnaire de l’une ou l’autre partie (`af-ter-dinner`). TeX, comme le Chicago Manual of Style, garde les parties entières et ne coupe un composé qu’à son trait d’union. `bodyText.hyphenation.compounds: false` fait de même : le dictionnaire laisse intact tout mot qui porte un trait d’union entre deux lettres, si bien qu’il ne se coupe qu’après ce trait d’union. Un trait d’union conditionnel tapé entre deux lettres du mot coupe toujours, et un composé plus large que toute la ligne est toujours coupé là où il le faut. La valeur par défaut, `true`, continue de couper les parties, ce qui donne à une colonne justifiée étroite davantage de moyens de remplir ses lignes.

L’orthographe portugaise demande que le trait d’union d’un composé coupé en fin de ligne soit répété au début de la ligne suivante (`vencer-` · `-se`), comme les règles de l’Académie espagnole depuis 2010 (`léxico-` · `-semántico`), pour que le lecteur sache que le trait d’union appartient au mot. `bodyText.repeatHyphen: true` le fait. Le trait d’union répété est mesuré avec sa ligne, si bien que le moteur de coupure lui réserve de la place, et il est peint comme le reste du texte ; dans le PDF, il porte un texte de remplacement qui l’omet, si bien que le texte copié ou extrait du PDF lit le mot une seule fois (`vencer-se`). La ligne l’enregistre comme `repeatedHyphen`, et ses `plainStart` et `sourceStart` pointent après lui, si bien que la correspondance avec la source, les liens et les titres courants lisent le mot tel qu’il est écrit. Une adresse web n’en reçoit jamais. Cela s’applique au texte courant, aux titres, aux listes, aux citations et aux encadrés ; un paragraphe sans mise en forme qui contient un composé est alors coupé par le moteur de coupure qui compose le texte mis en forme.

### Mots gardés entiers

On peut demander au dictionnaire de laisser certains mots tranquilles. `bodyText.hyphenation.exceptions` les nomme un par un : `'Alice'` n’est jamais coupé, `'You-Tube'` seulement au trait d’union de son entrée, comme avec `\hyphenation{…}` dans TeX. `bodyText.hyphenation.capitalized: false` garde entier tout mot qui commence par une majuscule, et `citations.hyphenateNames: false` les noms de famille des auteurs que cite le document. Le moteur de coupure a alors moins d’endroits où terminer une ligne : là où un nom était coupé, la ligne précédente prend plus ou moins d’espace entre ses mots, entre `minWordSpacing` et `maxWordSpacing`. Un mot plus large que toute sa ligne est toujours coupé. Voir [Mots gardés entiers](https://postext.dev/fr/docs/configuration-text.md#mots-gardés-entiers) pour savoir comment une entrée est reconnue.

### Où une ligne ne se coupe jamais

Certains endroits ne sont jamais des occasions de coupure, avec l’un ou l’autre moteur de coupure :

- **Une espace insécable.** U+00A0, l’espace fine insécable U+202F et l’espace tabulaire U+2007 soudent les mots de part et d’autre : un nombre et son unité, un renvoi de page, un groupe de milliers. Il en va de même dans les légendes, les cellules de tableau, les encadrés et le texte de la maquette (titres courants, ouvertures). Chacune garde sa propre largeur, telle qu’elle est mesurée ; la justification n’étire que les espaces entre les mots. Un caractère sans glyphe pour U+202F ou U+2007 reçoit la largeur que leur donnent les navigateurs, une demi-espace-mot et un chiffre, dans toutes les sorties, PDF compris ; pratiquement tous les caractères ont U+00A0. Un groupe soudé ainsi qui est plus large que toute la ligne fait exception, avec ou sans mise en forme en ligne : il se coupe à sa dernière espace insécable plutôt qu’à l’intérieur d’un mot. Le gluon de mots U+2060 soude sans occuper de place, en chinois aussi : deux caractères séparés par un gluon restent sur la même ligne. (Un nombre cerclé comme ① n’en a pas besoin : le texte chinois le garde avec le caractère qui suit depuis postext 1.25.) U+FEFF, l’espace insécable sans chasse, fait de même, bien que le gluon de mots soit le caractère prévu pour cela.
- **Une étiquette de renvoi avec un numéro.** Un renvoi en ligne qui imprime un numéro ou une page (`section 3.2`, `p. 112`, `Fig. 1.7`, `Theorem 2`) forme un seul mot : aucune ligne ne se coupe entre son mot et son numéro, quelle que soit l’espace que contient le modèle, et cette espace ne s’étire pas. Un renvoi qui imprime des mots (le texte d’une ancre, un titre, un remplacement `text="…"`) est différent depuis postext 1.28 : il se coupe à ses propres espaces, qui s’étirent avec la ligne (`crossRefs.breakLabels`) ; ses mots ne reçoivent toujours jamais de césure.
- **Du texte accolé.** Un mot en gras ou en italique et la ponctuation qui le suit (`**osmosis**.`), les parenthèses autour d’un renvoi en ligne (`(:ref{id="fig-3"})`), un mot composé en deux segments : du texte sans espace intermédiaire forme une seule unité, comme s’il s’agissait d’un seul mot. Quand une telle unité ne tient pas dans ce qui reste d’une ligne, elle descend entière. Quand elle ouvre une ligne et ne tient toujours pas, son dernier mot est coupé à une syllabe pour que la ponctuation descende avec la fin du mot ; seule une unité sans syllabe où couper laisse sa ponctuation ouvrir la ligne suivante.
- **À un trait d’union conditionnel qui n’a pas de lettre d’un côté.** Un trait d’union conditionnel (U+00AD) tapé dans le texte n’est une coupure qu’entre deux lettres ou chiffres : tapé entre `to` et `gether`, il permet à une ligne de se terminer sur `to-`. Tapé au bord d’un mot, après une parenthèse ou un guillemet ouvrant, ou avant une ponctuation fermante, il ne coupe rien et n’imprime rien, si bien qu’aucune ligne ne se termine sur un trait d’union après un mot entier. Et comme le dictionnaire laisse intact un mot qui porte déjà un trait d’union conditionnel, un trait tapé avant la première lettre garde entière cette occurrence du mot, dans toutes les langues : avec un trait avant son `A`, `Alice` n’est jamais coupé. InDesign écrit ainsi son trait d’union conditionnel, et le texte exporté depuis InDesign garde ce sens. Jusqu’à postext 1.27, un tel trait d’union conditionnel était une coupure comme une autre (`said Alice-`), et en catalan le reste du mot était quand même coupé.
- **À l’intérieur d’un mot d’une écriture liée.** Un mot arabe (ou syriaque, n’ko ou mongol) ne reçoit jamais de césure, pas même à un trait d’union conditionnel tapé dedans, et n’est jamais coupé entre ses lettres : ses lettres se lient, et un morceau isolé prendrait d’autres formes de lettres et une autre largeur. L’arabe et les autres langues qui s’écrivent de droite à gauche ont la césure désactivée par défaut ; quand elle est activée (pour les mots latins d’un livre arabe, ou dans un livre anglais qui cite de l’arabe), le dictionnaire ignore ces mots. Un mot composé en deux styles (`كتا**ب**`) reste un seul mot, mesuré entier. Un mot plus large que toute la ligne déborde de la ligne, et la construction signale un avertissement de contenu `unbreakableWordOverflow`.

Jusqu’à postext 1.4, un paragraphe avec une mise en forme en ligne ou un `:ref` pouvait se couper à une espace insécable, et une ligne en drapeau pouvait se terminer sur la `(` qui précède un renvoi ou s’ouvrir sur le point qui suit un mot en gras.

## Limites de l’espacement des mots

Le modèle de colle donne au moteur des limites explicites à l’étirement et au rétrécissement des espaces entre les mots. Ces limites sont réglées par deux propriétés de configuration de `bodyText` :

| Propriété | Défaut | Description |
| --- | --- | --- |
| `maxWordSpacing` | `2` | Limite supérieure de l’espacement des mots, en multiple de la largeur de l’espace normale. À la valeur par défaut, les espaces peuvent s’étirer jusqu’à 200 % de leur largeur naturelle. |
| `minWordSpacing` | `0.6` | Limite inférieure de l’espacement des mots, en multiple de la largeur de l’espace normale. À la valeur par défaut, les espaces peuvent rétrécir jusqu’à 60 % de leur largeur naturelle. |

Ces multiplicateurs se traduisent directement en valeurs `stretch` et `shrink` de la colle dans le modèle de Knuth-Plass :

```
stretchPerSpace = normalSpaceWidth × (maxWordSpacing - 1)
shrinkPerSpace  = normalSpaceWidth × (1 - minWordSpacing)
```

Aux valeurs par défaut (2 / 0,6), si la largeur de l’espace normale est de 4 px :
- Chaque espace peut s’étirer de 4 px (de 4 px à 8 px)
- Chaque espace peut rétrécir de 1,6 px (de 4 px à 2,4 px)

Des **limites plus strictes** (par exemple `maxWordSpacing: 1.2`) donnent un espacement plus uniforme mais laissent moins de marge de manœuvre à l’algorithme, ce qui peut entraîner davantage de césures ou, dans les cas extrêmes, un débordement. Des **limites plus souples** (par exemple `maxWordSpacing: 2.5`) donnent plus de latitude à l’algorithme mais tolèrent un espacement visiblement irrégulier sur certaines lignes.

Les valeurs par défaut de 2 et 0,6 privilégient la latitude de l’algorithme : elles laissent à Knuth-Plass assez de place pour éviter les débordements, les césures et les fins trop courtes dans les colonnes étroites, tout en restant largement dans les plages que la littérature typographique juge acceptables.

### Lignes que le moteur de coupure ne peut pas remplir

Knuth-Plass n’étire pas une ligne au-delà de `maxWordSpacing` quand il a le choix, et la correction des fins trop courtes non plus (voir [Pénalités d’orpheline, de veuve et de fin trop courte](https://postext.dev/fr/docs/justification.md#pénalités-dorpheline-de-veuve-et-de-fin-trop-courte)). Une ligne au-delà de la limite coûte plus que tout autre défaut qu’il pèse (un trait d’union, une dernière ligne courte, une ligne un peu plus lâche que ses voisines) dès l’instant où elle franchit la limite, et ce coût croît ensuite avec le carré de son taux d’étirement ; le moteur de coupure préfère donc couper un mot, répartir le jeu sur les lignes voisines ou composer deux lignes légèrement lâches plutôt qu’une seule beaucoup trop lâche.

Il arrive qu’aucun ensemble de coupures ne reste dans les limites. La ligne qui ne peut pas recevoir le mot suivant a trop peu d’espaces pour se partager le jeu : la première ligne sous un alinéa, suivie d’un mot trop long pour remonter et trop court pour être coupé ; une ligne de longs termes insécables ; une longue URL qui ne se coupe qu’à ses propres jointures ; un élément de liste dont le dernier mot ne remonte pas alors que chaque césure laisserait une fin trop courte. La fréquence dépend de la longueur de ligne, du caractère et de la langue. Dans une colonne de 83 mm d’anglais en 9,3 pt, 2 % à 8 % des lignes d’un chapitre dépassaient `maxWordSpacing` selon le caractère (le plus large en tête) ; le même chapitre en espagnol, dont la césure offre bien plus de coupures, n’en avait aucune. Knuth-Plass accepte alors la ligne la moins mauvaise, et ses espaces s’étirent au-delà de la limite. Quand elles s’étireraient à plus de 3× la largeur de l’espace normale, le moteur compose la ligne **en drapeau** : elle est dessinée à son espacement naturel et le bord droit cède, comme pour la dernière ligne d’un paragraphe. Le seuil est le même que celui de l’avertissement de ligne lâche du Sandbox. La ligne est composée en drapeau et signalée : un avertissement de contenu `raggedLine` indique son texte, sa page et le taux qu’il lui aurait fallu (dans le Sandbox, **Ligne justifiée composée en drapeau**, parmi les avertissements de composition ; un clic sélectionne la ligne), et `VDTLine.raggedSpaceRatio` conserve ce taux sur la ligne. Reformulez le texte ou donnez à la ligne un endroit où se couper. Les lignes sous le seuil restent telles qu’elles ont été mesurées. Il en va de même dans les encadrés, dont les lignes étroites sont l’endroit où cela arrive le plus ; jusqu’à postext 1.4, un encadré gardait une telle ligne justifiée, aussi loin que s’étirent ses espaces.

Pour réduire le nombre de ces lignes, dans cet ordre : vérifiez que la césure est activée et réglée sur la langue du texte ; allongez la ligne ou réduisez le corps du texte ; laissez les lignes prendre un peu d’approche (section suivante) ; ou augmentez `maxWordSpacing`, ce qui permet aux mêmes lignes de compter comme étant dans la limite sans les composer autrement.

### L’approche en dernier recours

`bodyText.maxJustifyTracking` permet à une telle ligne de prendre un peu d’approche (l’espacement des lettres) au lieu de plus d’espace entre les mots, jusqu’à ce nombre de millièmes de cadratin par caractère (10 = 0,01 em, l’unité d’InDesign). Cela marche dans les deux sens : une ligne dont les espaces s’étireraient au-delà de `maxWordSpacing` écarte ses lettres jusqu’à ce que ses espaces reviennent à la limite, et une ligne qui ne tiendrait qu’avec des espaces sous `minWordSpacing` resserre ses lettres au lieu de renvoyer un mot à la ligne suivante. Knuth-Plass en tient compte quand il choisit les coupures : une ligne approchée coûte un peu plus qu’une ligne dans les limites et bien moins qu’une ligne au-delà, si bien que l’approche n’est prise que là où l’espacement des mots seul échouerait. Les lignes dans les limites, la dernière ligne d’un paragraphe (sauf si elle déborde), une ligne d’un seul mot et une ligne qui contient une pastille n’en prennent jamais. La ligne l’enregistre comme `VDTLine.letterSpacing`, en plus de l’approche propre à son bloc, et le canvas, le HTML et le PDF la peignent. Les mots d’une écriture liée (l’arabe…) ne prennent jamais d’approche, ni celle-ci ni aucune autre (le `letterSpacing` d’un style, l’équilibrage des colonnes, la correction des fins trop courtes) : écarter leurs lettres rompt les liaisons. Une ligne ne compte aucune de leurs lettres, et les moteurs de rendu les peignent sans approche ; un style qui règle `letterSpacing` sur un tel texte reçoit un avertissement de contenu `joiningScriptLetterSpacing`.

```ts
bodyText: {
  maxWordSpacing: 2,
  maxJustifyTracking: 10, // at most 0.01 em a letter, either way
}
```

C’est désactivé par défaut, si bien que les documents gardent leurs lignes s’ils ne le demandent pas. Dans le chapitre anglais cité plus haut, 10 ‰ a ramené les lignes au-delà de `maxWordSpacing` à une ou zéro par caractère, et 20 ‰ à zéro pour trois caractères sur quatre. Restez modeste : au-delà de 20 ‰ environ, l’approche commence à se voir sous la forme d’une ligne plus claire ou plus sombre.

### La kashida dans le texte arabe

Une ligne d’arabe justifiée ne reçoit pas d’approche : ses lettres se lient, et les écarter rompt les liaisons. Elle s’étire plutôt à deux endroits, ses espaces entre les mots et ses kashidas, les liaisons allongées qu’un calligraphe trace entre deux lettres liées (كتاب → كتـاب). Postext les insère sous forme de tatwīls (U+0640, ـ) entiers, que la police dessine d’un seul trait ; Amiri transforme une suite d’un à sept tatwīls en kashida courbe.

`bodyText.kashida` vaut `'auto'` par défaut dans un document dont la langue s’écrit en alphabet arabe (`ar`, `fa`, `ur`…) et `'none'` dans toutes les autres ; réglez-le sur `'auto'` pour allonger les mots arabes cités dans un livre dans une autre langue. Chaque ligne sauf la dernière d’un paragraphe voit d’abord ses espaces entre les mots s’ouvrir jusqu’à un quart de leur largeur, puis des kashidas pour le reste de son jeu, un tatwīl à la fois, aux meilleurs endroits d’abord et tour après tour, chaque mot étant remesuré entier avec ses tatwīls ; ce qui reste, moins d’un tatwīl, retourne aux espaces. Knuth-Plass compte l’allongement possible de chaque mot comme de l’étirement, si bien qu’il choisit les coupures en sachant qu’une ligne de mots arabes peut s’élargir à ces endroits.

L’emplacement possible d’une kashida découle des règles de [raqim-kashida](https://github.com/aliftype/raqim-kashida) (MIT) : seulement entre deux lettres qui se lient, jamais après une lettre qui ne se lie pas à la suivante (ا د ذ ر ز و ة), jamais en fin de mot, jamais à l’intérieur d’un lām-alif, après les signes de la lettre qui la précède. `kashidaPatterns: 'naskh'` suit les règles classiques du naskh (la matrice de Benatia avec les interdictions d’Afifi : rien après kāf ou lām, rien avant ṣād, ʿayn ou wāw, un hāʾ de pronom final allongé juste avant lui) ; `'simple'`, les priorités de Microsoft pour les caractères modernes simples ; `'nastaliq'`, les règles du naskh adaptées au nastaʿlīq. La valeur par défaut, `'auto'`, lit la police du texte courant : aucune kashida dans un caractère ruqʿa ou dīwānī comme Aref Ruqaa, les règles du nastaʿlīq dans un caractère nastaʿlīq, le naskh sinon. Un mot prend au plus `kashidaPerWord` allongements (1 par défaut) d’au plus `kashidaMaxLength` em (0,6, trois tatwīls d’Amiri). Un mot qui contient un tatwīl tapé par l’auteur est allongé à cet endroit. Les mots latins, les chiffres, les titres, les lignes en drapeau et la dernière ligne d’un paragraphe n’en prennent jamais.

```ts
bodyText: {
  kashida: 'auto',          // the default in an Arabic book
  kashidaPatterns: 'naskh', // 'auto' picks it from the font
  kashidaMaxLength: 0.6,    // em
}
```

Les tatwīls font partie du texte peint (chaque segment les liste dans `VDTLineSegment.kashida`, et la ligne les compte dans `VDTLine.kashida`) ; le texte brut, la correspondance avec la source, les liens et le texte copié depuis le PDF les omettent.

### La dernière ligne

Dans le modèle boîte-colle-pénalité, chaque paragraphe se termine par une colle de largeur 0, infiniment extensible, suivie d’une coupure forcée. Cette colle finale absorbe sans coût tout l’espace qui reste sur la dernière ligne, si bien que les dernières lignes sont naturellement en drapeau : elles sont rendues à leur largeur naturelle, et `justifiedSpaceRatio` n’est calculé que pour les lignes autres que la dernière.

Il y a une exception. Knuth-Plass peut accepter une dernière ligne dont le contenu naturel est *plus large* que la longueur de ligne, en supposant que sa colle entre les mots rétrécira : c’est la sémantique standard du réglage de la colle dans TeX. Les trois moteurs de rendu détectent ce cas (la largeur naturelle du contenu de la ligne dépasse la longueur de ligne effective) et compriment les espaces entre les mots de cette dernière ligne pour qu’elle tienne exactement dans la longueur de ligne au lieu de déborder. La vérification s’applique de la même façon dans les moteurs de rendu canvas, PDF et HTML.

### Coupure optimale ou gloutonne

La propriété `bodyText.optimalLineBreaking` (par défaut : `true`) détermine l’algorithme de coupure des lignes qu’utilise le moteur :

- **`true`** : l’algorithme de programmation dynamique de Knuth-Plass. Il évalue tous les ensembles de coupures possibles et retient celui qui est globalement optimal. C’est le réglage recommandé pour tout texte justifié. Le texte courant en drapeau en bénéficie aussi avec `bodyText.optimalRagged` (activé par défaut) : les espaces entre les mots gardent leur largeur et le moteur de coupure pèse plutôt ce qui manque à chaque ligne pour atteindre la pleine largeur, une ligne trop courte de 3 em coûtant autant qu’une ligne justifiée à `maxWordSpacing`, si bien que le bord en drapeau est plus régulier et que les règles des fins trop courtes s’appliquent.
- **`false`** : la méthode gloutonne (*first-fit*), assurée par `layoutNextLine()` de Pretext. Plus rapide, mais de moindre qualité. Ne l’utilisez que lorsque les performances comptent plus que la qualité typographique (par exemple un aperçu en temps réel sur un très grand nombre de caractères).

Quand Knuth-Plass est actif et ne produit aucune coupure valide (ce qui peut arriver avec des colonnes extrêmement étroites ou des mots plus longs que la largeur de la colonne), le moteur se rabat automatiquement sur la coupure gloutonne pour ce paragraphe.

### Lignes avec tabulations

Un paragraphe qui contient une tabulation (voir [Format du document › Tabulations et taquets](https://postext.dev/fr/docs/document-format.md#tabulations-et-taquets)) est composé ligne par ligne, comme les traitements de texte et InDesign composent le texte tabulé, jamais avec Knuth–Plass, quoi qu’indique `optimalLineBreaking`. L’endroit où tombe une tabulation dépend de l’endroit où en est sa ligne, alors que la recherche optimale additionne les largeurs de chaque ligne candidate comme si ce n’était pas le cas. Il en va de même pour le texte chinois et japonais : un paragraphe avec une tabulation se coupe entre les caractères selon les règles de coupure des lignes du document, sans compression de la ponctuation ni justification entre les caractères.

Dans un paragraphe justifié, une ligne qui contient une tabulation n’est pas étirée avant sa dernière tabulation, puisque les taquets fixent le début de chacune de ses parties. Seules les espaces entre les mots situées après la dernière tabulation absorbent le jeu, et seulement si la ligne n’est pas la dernière du paragraphe ; une ligne sans espace entre les mots après sa dernière tabulation est composée telle quelle. Les lignes en drapeau ne sont pas concernées. La ligne enregistre qu’elle contient une tabulation (`VDTLine.tabbed`) ; chaque tabulation est un segment d’espace qui porte son taquet (`VDTLineSegment.tab`), et chaque ligne de points de conduite forme un segment à part (`VDTLineSegment.leader`, `'text'` ou `'rule'`).

L’équilibrage des colonnes ne recoupe jamais un tel paragraphe de façon plus lâche et ne lui applique jamais d’approche : il reste tel qu’il a été mesuré, si bien que ses taquets ne bougent pas.

### Chinois, japonais et coréen

Un paragraphe qui contient plus de caractères CJK que d’espaces entre les mots est composé par le compositeur CJK, et non par Knuth-Plass. Chaque intervalle entre deux caractères est un endroit où une ligne peut se couper (sauf là où les règles de coupure des lignes de `cjk.lineBreak` l’interdisent), si bien qu’une recherche optimale n’a rien à peser ; les lignes sont remplies l’une après l’autre. Quand un caractère ne tient pas, la ligne essaie d’abord de l’absorber en cédant du blanc de ponctuation et de l’espace (resserrement : les espaces entre les mots jusqu’à un quart de cadratin, puis les points médians, les parenthèses, les virgules d’énumération, les espaces entre han et latin jusqu’à un huitième de cadratin, et les signes de fin de phrase, dans la mesure où `cjk.punctuationWidth` le permet) ; c’est seulement quand cela ne suffit pas qu’un caractère qui ne peut pas ouvrir une ligne emmène avec lui le caractère précédent. Voir [Typographie d’Asie de l’Est](https://postext.dev/fr/docs/configuration-east-asian.md#typographie-dasie-de-lest) pour les règles. [Mise en page chinoise](https://postext.dev/fr/docs/chinese-layout.md) couvre le reste de la composition chinoise : les largeurs de ponctuation selon la région, la grille de caractères et le texte vertical. Un document japonais suit plutôt la JLReq (voir [Mise en page japonaise](https://postext.dev/fr/docs/japanese-layout.md#justification-et-dernière-ligne)) : ses niveaux de kinsoku, une ligne qui rend de l’espace dans l’ordre de la JLReq §3.8.3 (d’abord le demi-cadratin après le signe qui termine la ligne, en entier ou pas du tout, l’espace après 。 jamais à l’intérieur de la ligne), et aucun espace ajouté après une parenthèse ouvrante, avant une parenthèse fermante ni à côté de 、。・：；？！ quand une ligne est espacée.

Une ligne justifiée d’un tel paragraphe, sauf la dernière, est étendue jusqu’à la longueur de ligne dans l’ordre que fixe la clreq (§6.2.2.4) :

1. Les espaces entre les mots occidentaux, à parts égales, jusqu’à un demi-cadratin chacune.
2. Les espaces entre han et latin (`cjk.latinSpacing`), à parts égales, jusqu’à un demi-cadratin chacune.
3. Chaque intervalle entre les caractères, à parts égales, y compris les espaces entre han et latin : entre caractères han, entre un han et une ponctuation, entre un han et un mot latin. Jamais à l’intérieur d’un mot latin, d’un nombre, d’un tiret de deux cadratins ou de points de suspension, et jamais à côté d’un signe de liaison (～, –, un — seul) ou d’une barre oblique. Un signe suspendu au-delà de la fin de la ligne (`cjk.hangingPunctuation`) reste hors de la longueur de ligne.

La dernière ligne d’un paragraphe est composée sans espacement. L’espacement est enregistré par segment de ligne (`VDTLineSegment.tracking`), si bien que le canvas, le HTML et le PDF peignent les mêmes positions, et le Sandbox place le curseur sur le caractère cliqué.

Une ligne qui aurait besoin de plus d’un demi-cadratin entre ses caractères (ou de plus que `bodyText.maxJustifyTracking`, quand il est réglé) est composée avec cette valeur, en deçà de la longueur de ligne, et signalée par un avertissement de contenu `cjkLooseLine` (dans le Sandbox, **Ligne CJK composée trop courte**). La cause habituelle est un long mot latin ou une adresse web qui n’a pas pu remonter. Une ligne sans aucun caractère CJK, comme le début d’une longue adresse web, est composée en drapeau sans l’avertissement.

Pour la mise en évidence des lignes lâches, une ligne CJK compte comme lâche d’après son espacement : son taux vaut 1 plus son espacement en huitièmes de cadratin, si bien qu’au seuil par défaut de 3, une ligne dont les caractères sont écartés de plus d’un quart de cadratin est signalée (`lineLooseness(line, fontSizePx)` donne ce taux). L’équilibrage des colonnes peut allonger d’une ligne un paragraphe CJK : il coupe les lignes un peu en deçà de la longueur de ligne, un huitième de cadratin à la fois jusqu’à deux cadratins, et les étend ensuite jusqu’à celle-ci. Sur une grille de caractères, il ne peut pas les étendre de plus de `headings.balancing.maxTracking` entre deux caractères, si bien que les caractères gardent leurs cases (voir [Justification verticale : l’équilibrage des colonnes](https://postext.dev/fr/docs/justification.md#justification-verticale-léquilibrage-des-colonnes)).

## Débogage des lignes lâches

Même avec Knuth-Plass et la césure, certaines lignes seront plus lâches que l’idéal, surtout dans les colonnes étroites aux mots longs, ou dans les langues qui offrent peu de possibilités de césure. La fonction de débogage de **mise en évidence des lignes lâches** vous aide à trouver ces lignes problématiques immédiatement.

### Fonctionnement

Chaque ligne justifiée de l’arbre de document virtuel porte un `justifiedSpaceRatio` : la largeur à laquelle ses espaces sont peintes, rapportée à l’espace du style romain. Une valeur de 1,0 signifie l’espace romaine ; 2,5 signifie des espaces 2,5 fois plus larges. Une ligne qui contient des espaces d’un autre style (une expression en italique, un mot en gras) porte un second nombre, `justifiedSpaceLimitRatio`, son espacement des mots tel que l’encadrent `minWordSpacing` et `maxWordSpacing`. `lineWordSpacing(line)` donne le nombre à comparer aux limites : le second quand la ligne en a un, le premier sinon.

Quand `debug.looseLineHighlight.enabled` vaut `true`, le Sandbox peint une surimpression semi-transparente sur son aperçu Canvas, sur chaque ligne dont l’espacement des mots (`lineWordSpacing`) dépasse le seuil `threshold` configuré. Une ligne composée exactement à `maxWordSpacing` qui contient une espace en gras est peinte quelques centièmes d’espace au-delà et n’est pas comptée : le moteur de coupure a respecté la limite. Le seuil par défaut est de 3,0 : seules les lignes dont les espaces sont trois fois plus larges que la normale sont mises en évidence. C’est une barre délibérément haute ; des lignes aussi lâches sont de vrais problèmes typographiques. C’est aussi la largeur au-delà de laquelle le moteur compose une ligne justifiée en drapeau au lieu de l’étirer (voir [Lignes que le moteur de coupure ne peut pas remplir](https://postext.dev/fr/docs/justification.md#lignes-que-le-moteur-de-coupure-ne-peut-pas-remplir)), si bien qu’à la valeur par défaut, la mise en évidence et l’avertissement `looseLines` ne trouvent presque rien dans le texte courant : une telle ligne est composée en drapeau et signalée par un avertissement `raggedLine`. Abaissez le seuil, à 1,5 ou 2, pour trouver les lignes lâches mais encore justifiées.

La surimpression ne fait pas partie de la page, si bien que les pages exportées ne la montrent jamais. Pour la dessiner sur un canvas à vous, appelez `drawLooseLines(ctx, page, doc, { threshold })` juste après `renderPageToCanvas` ; `findLooseLines(doc, { threshold })` renvoie les mêmes lignes sous forme de données (voir [Lignes lâches dans votre propre canvas](https://postext.dev/fr/docs/configuration-fonts-colors-viewers.md#lignes-lâches-dans-votre-propre-canvas)).

### Configuration

La mise en évidence des lignes lâches fait partie de la section `debug` de `PostextConfig` :

| Propriété | Type | Défaut | Description |
| --- | --- | --- | --- |
| `looseLineHighlight.enabled` | `boolean` | `false` | Mettre ou non en évidence les lignes lâches. |
| `looseLineHighlight.color` | `ColorValue` | `#ff000040` | Couleur de la surimpression. Par défaut, un rouge semi-transparent. |
| `looseLineHighlight.threshold` | `number` | `3` | Multiple de la largeur de l’espace normale au-delà duquel une ligne est considérée comme lâche. Des valeurs plus basses repèrent plus de lignes ; des valeurs plus hautes ne mettent en évidence que les pires cas. À partir de 3, le texte courant ne montre presque rien, puisque les lignes au-delà de 3× sont composées en drapeau. |

```ts
debug: {
  looseLineHighlight: {
    enabled: true,
    threshold: 2.5,
    color: { hex: '#ff660040', model: 'hex' },
  },
}
```

### Interpréter les résultats

Quand vous activez la mise en évidence des lignes lâches et voyez des bandes rouges sur certaines lignes, cela signifie que le moteur n’a pas trouvé comment composer ces lignes sans un espacement des mots excessif. Commencez par le haut : les causes sont classées de la plus probable à la moins probable.

| Cause | Solution |
| --- | --- |
| La colonne est trop étroite pour le corps du texte | Élargissez la colonne, réduisez le corps ou passez à une mise en page sur une seule colonne. |
| Mots longs avec peu de points de césure | Vérifiez que la césure est activée et que la bonne langue est réglée. Certains termes techniques ou noms propres n’ont aucun point de coupure valide. |
| La césure est désactivée | Activez `bodyText.hyphenation.enabled`. La justification sans césure est presque toujours moins bonne. |
| Quelques lignes restent lâches quel que soit le caractère | Laissez ces lignes prendre un peu d’approche : `bodyText.maxJustifyTracking: 10`. Voir [L’approche en dernier recours](https://postext.dev/fr/docs/justification.md#lapproche-en-dernier-recours). |
| Les limites d’espacement des mots sont trop strictes | Augmentez légèrement `maxWordSpacing` (par exemple de 2 à 2,4). L’algorithme dispose ainsi de plus de place. |
| La langue a de longs mots composés (par exemple l’allemand) | Vérifiez que la bonne langue est réglée. Les motifs de césure allemands gèrent bien les mots composés, mais seulement si le moteur sait que le texte est en allemand. |

## Justification verticale : l’équilibrage des colonnes

Knuth-Plass résout le problème *horizontal* : l’endroit où chaque ligne se coupe. L’équilibrage des colonnes résout son pendant *vertical* : l’endroit où chaque colonne se termine. Les éditeurs attendent que chaque colonne d’une page commence en haut et s’arrête au ras du bas de la page, ligne pour ligne sur toute la double page. Les règles mêmes qui protègent la qualité du texte vont à l’encontre de cet objectif : la protection contre les orphelines et les veuves, les titres `keepWithNext` et les figures insécables renvoient du contenu à la colonne suivante avant que la colonne en cours soit pleine, et laissent une ou plusieurs lignes vides de la grille de ligne de base à son pied.

Quand `headings.balancing` est activé (c’est le cas par défaut, sauf en texte vertical et sur une grille de caractères), le moteur comble ces vides comme le ferait un compositeur, en appliquant ses leviers dans un ordre de priorité éditoriale strict ; chaque levier n’agit que sur ce que le précédent n’a pas pu absorber :

1. **Un encadré qui ferme la colonne.** Un encadré qui termine une colonne courte descend exactement de l’espace libre sous son pied, si bien que son bord inférieur rejoint la dernière ligne de la grille, au niveau de la colonne voisine ; cet espace peut être une fraction de ligne. Ce levier passe en premier et prend tout le vide, sauf si `closingBox` en décide autrement : `'last'` laisse d’abord les leviers suivants prendre les lignes entières et ne donne à l’encadré que ce qu’ils laissent, si bien qu’un encadré qui commente le paragraphe au-dessus reste près de lui ; `'off'` ne déplace jamais l’encadré.
2. **Les images à zone de sécurité.** Une image dont la ressource marque une zone de sécurité (`Resource.safeArea`, la partie qui doit toujours rester visible ; voir [Format du document › Zone de sécurité](https://postext.dev/fr/docs/document-format.md#zone-de-sécurité)) grandit de lignes entières de la grille quand elle est placée dans le texte de la colonne courte, ou en flottant en tête ou au pied de cette seule colonne : le moteur rogne ce qui se trouve hors de la zone de sécurité pour l’agrandir en hauteur, sur toute sa largeur. Une image plus haute ne laisse aucun trou sur la page, c’est pourquoi elle passe avant tout ajout d’espace. Un flottant en tête de colonne sur une page ou une bande de fin, dont les têtes de colonne restent alignées, ne grandit pas.
3. **L’espace au-dessus des titres.** Des lignes entières de la grille de ligne de base sont ajoutées à la marge supérieure des titres de la colonne courte. Quand il faut plusieurs lignes et que la colonne contient plusieurs titres, les lignes sont réparties à tour de rôle par ordre d’importance, si bien que le titre le plus important reçoit toujours la plus grosse part : 3 lignes réparties sur un `h2` et un `h3` donnent +2 au-dessus du `h2` et +1 au-dessus du `h3`. Les titres tout en haut d’une colonne ne reçoivent jamais d’espace supplémentaire (les colonnes continuent de commencer en haut de la page), et un titre en fin de colonne n’est jamais poussé vers le bas de celle-ci. Un titre prend au plus `maxLinesBeforeLoose` lignes de cette façon (deux par défaut) avant que les leviers suivants soient essayés, et ses lignes supplémentaires, jusqu’à `maxLinesPerHeading`, seulement quand ces leviers n’ont plus rien à donner.
4. **L’espace après la fin des listes.** Une ligne de la grille est ajoutée là où se termine une liste ou une énumération : un peu d’air après une liste se lit naturellement. Le nombre est plafonné par fin de liste (`maxLinesAfterList`, 1 par défaut). Viennent ensuite une ligne sous une formule centrée ou un encadré après lequel le texte reprend (`stretchAfterDisplays`), et une sous une figure ou un tableau en tête de colonne (`stretchAfterFloats`, `maxLinesAfterFloat`). Le texte sous la figure peut être la suite d’un paragraphe commencé à la page précédente ; il descend quand même, de la ligne que les règles de coupure ont laissée libre au pied de la colonne (une ligne que la règle des veuves a laissée vide, un espace entre paragraphes sans place pour du texte après lui). Sous une figure qui s’étend sur plusieurs colonnes, la ligne n’est ajoutée que lorsque chaque colonne sous la figure la prend, pour que les têtes restent alignées, ou quand elle ferme sa colonne au ras (`stretchAfterWideFloats`).
5. **Les paragraphes lâches.** En dernier recours, des paragraphes de la colonne sont recoupés avec une ligne de plus (le `\looseness=+1` de TeX), jusqu’à `maxLooseParagraphs` d’entre eux (deux par défaut), une ligne supplémentaire chacun. Le moteur de coupure relance Knuth-Plass en demandant exactement une ligne de plus et n’accepte le résultat que si **chaque ligne de la solution lâche reste sous `maxWordSpacing`** : le gris typographique ne dépasse jamais la limite que vous avez déjà configurée. Le moteur préfère les paragraphes les plus longs de la colonne, où l’espace supplémentaire se dilue dans le plus grand nombre d’espaces entre les mots et devient invisible. Ce levier exige `optimalLineBreaking` (le réglage par défaut) ; les paragraphes coupés entre deux colonnes sont exclus. Un paragraphe que la correction des fins trop courtes a composé avec une ligne de moins compte sa ligne supplémentaire à partir de cette composition : il peut regagner la ligne que la correction lui a retirée, mais seulement avec une composition sans fin trop courte et sans ligne au-delà de `maxWordSpacing` (jusqu’à postext 1.4, il pouvait revenir avec une ligne bien au-delà de la limite).

   Quand l’espacement des mots seul ne suffit pas à gagner la ligne, le paragraphe peut aussi prendre un peu d’**approche** positive, la correction classique du compositeur. Le moteur essaie d’abord la plus petite valeur (la moitié de `maxTracking`, puis `maxTracking`, en millièmes de cadratin par caractère ; 10 = 0,01 em par défaut) et garde la première qui fait gagner la ligne, toujours sous la même limite d’espacement des mots. L’approche est prise en compte dans la mesure des lignes du paragraphe et peinte par chaque moteur de rendu (`letterSpacing` du canvas, `letter-spacing` en CSS, espacement des caractères en PDF). Désactivez-la avec `trackParagraphs: false`.

### Pourquoi la correction reste locale

Les sauts de colonne sont *liés à un élément* : l’élément qui ouvre la colonne suivante s’y trouve parce qu’il ne tenait pas dans le vide. Faire descendre la fin d’une colonne d’au plus son propre vide ne déplace donc jamais de contenu vers la colonne ou la page suivante : chaque colonne est réglée sur place, sans recomposition en cascade. Le moteur le vérifie tout de même de façon empirique : il relance le placement avec les ajustements proposés (jusqu’à 8 passes), mesure le vide total restant et garde toujours la meilleure mise en page trouvée. Un paragraphe lâche qui ne parvient pas à gagner sa ligne dans la limite d’espacement, quelle que soit l’approche, est mis sur liste noire et le candidat suivant est essayé.

### Quand une colonne courte est laissée telle quelle

Une colonne courte est parfois le résultat *correct*, et l’équilibrage sait s’effacer :

- La **dernière colonne d’une page** n’est équilibrée que si la page s’enchaîne naturellement sur la suivante. Les pages terminées par `:::pagebreak`, par le `breakBefore` d’un titre ou par une ouverture de chapitre gardent leur dernière colonne courte : un chapitre se termine légitimement au milieu d’une page.
- La **dernière page du document** n’est jamais équilibrée.
- Une colonne **sans point d’étirement utilisable** (aucun titre admissible, aucune fin de liste, aucun paragraphe pouvant être relâché) garde son vide plutôt que de dégrader la typographie.
- Un **paragraphe qui contient une tabulation** n’est jamais relâché ni approché (voir [Lignes avec tabulations](https://postext.dev/fr/docs/justification.md#lignes-avec-tabulations)).

```js
const doc = buildDocument(content, {
  headings: {
    balancing: {
      enabled: true,            // default
      maxLinesPerHeading: 4,    // cap per heading
      maxLinesBeforeLoose: 2,   // lines before the other levers
      stretchAfterLists: true,  // lever 4
      maxLinesAfterList: 1,     // cap per list end
      stretchAfterDisplays: true, // under a formula or a box
      looseParagraphs: true,    // lever 5 — bounded by bodyText.maxWordSpacing
      maxLooseParagraphs: 2,    // loose paragraphs per short column
      trackParagraphs: true,    // let a loose paragraph take a little tracking
      maxTracking: 10,          // ‰ em per character (0.01 em)
      closingBox: 'first',      // lever 1: 'first' | 'last' | 'off'
    },
  },
});
```

Dans le Sandbox, ces réglages se trouvent dans la section *Titres* de **Maquette → Titres et table des matières** (« Équilibrer les colonnes », avec « Étirer après les listes », « Étirer sous les figures », « Encadré en fin de colonne » et « Paragraphes lâches » imbriqués dessous). Activez la grille de ligne de base (**Maquette → Avancé → Aides à l’écran**) et regardez la vue Canvas pour voir l’effet : sans équilibrage, les colonnes courtes s’arrêtent au-dessus de la dernière ligne de la grille ; avec, chaque colonne équilibrable se ferme sur la même ligne. Voir la page [Configuration](https://postext.dev/fr/docs/configuration-text.md#équilibrage-des-colonnes) pour la référence complète des options.

## Exemple complet

Une configuration complète qui réunit tous les réglages de césure et de justification :

```ts
import { buildDocument } from 'postext';

const vdt = buildDocument(content, {
  bodyText: {
    fontFamily: 'EB Garamond',
    fontSize: { value: 9, unit: 'pt' },
    textAlign: 'justify',

    // Knuth-Plass optimal line breaking (default: true)
    optimalLineBreaking: true,

    // Hyphenation
    hyphenation: {
      enabled: true,
      locale: 'es',
    },

    // Word spacing bounds (multipliers of normal space width)
    maxWordSpacing: 2,     // spaces stretch up to 200%
    minWordSpacing: 0.6,   // spaces shrink down to 60%
  },

  // Debug: highlight lines with excessive spacing
  debug: {
    looseLineHighlight: {
      enabled: true,
      threshold: 2.5,
      color: { hex: '#ff000040', model: 'hex' },
    },
  },
});
```

Pour la liste complète des options de configuration du texte courant, voir la page [Configuration](https://postext.dev/fr/docs/configuration-text.md#texte-courant). Pour savoir comment la chaîne de mise en page utilise ces réglages lors de la mesure du texte, voir la page [Architecture](https://postext.dev/fr/docs/architecture.md).
