# Bande dessinée

> Comment Postext compose la bande dessinée et le manga à partir de Markdown : planches découpées par un arbre de découpage, dessins des cases recadrés autour de leur zone de sécurité, lettrage produit à partir d’un script (paroles, pensées, chuchotements, cris, radio, récitatifs, onomatopées), styles de bulles, ancres des personnages sur les dessins, strips dans le texte, doubles pages, lecture de droite à gauche et lettrage vertical, traductions, et sorties canvas, PDF, HTML et EPUB

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

## En bref

Cette page explique comment faire une bande dessinée avec Postext. Les dessins sont les cases, et tout ce que les personnages disent, pensent ou entendent s’écrit comme du texte dans le fichier Markdown. Postext place les cases sur la page et dessine lui-même les bulles, les récitatifs et les onomatopées autour des mots. Pour traduire la BD, vous ne réécrivez que les mots, et Postext refait le lettrage dans la nouvelle langue : le japonais en colonnes, l’arabe de droite à gauche. La page traite aussi des strips dans un livre, des planches sur deux pages en regard, des sorties et des limites qui restent.

**Les dessins sont les cases ; tout ce qui est dit, pensé, raconté ou entendu est du texte dans le Markdown.**

Une bande dessinée, dans Postext, c’est un ensemble de dessins et un script. Les dessins sont des ressources bitmap ou SVG ordinaires, dessinées sans aucun lettrage. Le script se trouve dans le Markdown du chapitre : un bloc `:::page` découpe une planche en cases, donne à chaque case son dessin et énumère, ligne par ligne, les bulles, les récitatifs et les onomatopées de cette case. Postext dispose les cases, recadre chaque dessin dans sa cellule sans entamer ce qui compte, compose le texte dans le caractère de lettrage du livre, à un seul corps, trace une bulle autour, dirige l’appendice vers le personnage qui parle et place les bulles dans l’ordre de lecture, là où elles ne couvrent aucun visage. Une traduction est un autre fichier Markdown avec les mêmes cases et d’autres mots ; Postext en refait entièrement le lettrage, en colonnes pour le japonais et de droite à gauche pour l’arabe.

La bande dessinée est facultative : un document sans `:::page` ni `:::strip` se compose exactement comme avant. Les réglages se trouvent dans la [section `comics` de la configuration](https://postext.dev/fr/docs/configuration-comics.md#bande-dessinée), et le balisage est résumé dans [Format du document › Bande dessinée](https://postext.dev/fr/docs/document-format.md#bandes-dessinées).

## Planches

Un bloc `:::page` est une planche. Il s’ouvre par une ligne `:::page{…}`, contient une ligne `::panel` par case, chacune suivie du script de cette case, et se ferme par un `:::` seul :

```md
:::page{split="30 [30 | 20 | *] / *" gutter=4mm}
::panel{art=lh-arrive}
caption: Every summer, Maya spent a week at the lighthouse.
maya: Grandpa! I'm here!
::panel{art=lh-radio}
tomas: Just in time. The radio's gone deaf,
tomas: and there's a storm on the way.
::panel{art=lh-maya}
maya{thought}: That valve looks loose…
::panel{art=lh-beam bleed}
sfx{rotate=-8}: KRAK
tomas{shout}: Hold on to the rail!
:::
```

Le bloc commence toujours sur une nouvelle page et l’occupe en entier. Le texte qui suit la clôture commence à la page suivante : deux blocs `:::page` à la suite donnent donc deux planches, et un `:::pagebreak` entre eux n’ajoute aucune page. Le moteur lit lui-même le corps du bloc, ligne par ligne, comme il lit `:::verse` : les blocs Markdown, les conteneurs et les autres directives n’y sont pas reconnus, et les `<!-- comments -->` sont ignorés, y compris sur plusieurs lignes.

Le rôle de la page est `'comic'` (`VDTPage.role`). Elle ne porte ni titre courant ni folio, puisqu’une planche n’est faite que de cases, mais elle compte dans la numérotation des pages ; `comics.runningHeads: true` les imprime. Les cases sont découpées dans le **cadre**, c’est-à-dire le bloc de texte de la page à l’intérieur de ses marges, sauf si `comics.frame.margins` donne aux planches leurs propres marges.

Les attributs de la ligne d’ouverture :

| Attribut | Effet |
| --- | --- |
| `split="…"` | La façon dont le cadre est découpé en cellules (voir [Découper la planche](https://postext.dev/fr/docs/comics.md#découper-la-planche)). Sans cet attribut, les cases sont empilées en bandes de même hauteur. |
| `gutter=4mm`, `gutter="4mm 2mm"` | L’espace entre les bandes, et entre les cases côte à côte quand une seconde valeur est donnée. Un nombre seul est en millimètres. Par défaut : `comics.gutter` (4 mm et 2 mm). |
| `style=…` | Un style de case nommé pour toutes les cases de la planche (`comics.panelStyles`). |
| `bleed` | Toute case qui touche le cadre s’étend jusqu’à la coupe et dans le fond perdu. |
| `direction=rtl`, `dir=ltr` | Le sens de lecture de cette planche, quoi que dise la configuration (voir [Sens de lecture](https://postext.dev/fr/docs/comics.md#sens-de-lecture)). |
| `spread` | Étaler la planche sur deux pages en regard (voir [Doubles pages](https://postext.dev/fr/docs/comics.md#doubles-pages)). |

## Découper la planche

L’attribut `split` décrit la planche comme un arbre de coupes. Une liste de tailles séparées par `/` empile des cellules de haut en bas (les bandes) ; une liste séparée par `|` les place côte à côte, à partir du côté où commence la lecture. Une taille suivie d’une liste entre crochets découpe à nouveau cette cellule selon l’autre axe. La planche de l’exemple ci-dessus, `30 [30 | 20 | *] / *`, est une bande de 30 % de la hauteur du cadre, découpée en cases de 30 % et 20 % de sa largeur et une troisième qui prend le reste, au-dessus d’une seconde bande qui prend le reste de la planche :

> **Figure: Le découpage 30 [30 | 20 | *] / ***
> Le cadre d’une planche découpé par 30 [30 | 20 | *] / *. Une première bande occupe 30 % de la hauteur et se divise en trois cases : 30 % de la largeur, 20 % et le reste. Une seconde bande, en dessous, occupe le reste de la planche en une seule case. Les gouttières sont à cheval sur les lignes de coupe. À côté de la planche, le découpage est dessiné sous forme d’arbre : une liste de rangées à deux éléments, dont le premier contient une liste de trois colonnes entre crochets. Les feuilles sont les cases 1 à 4 dans l’ordre de lecture.
>
> *Les tailles sont des pourcentages de la cellule parente ; * prend ce qui reste. Les cases sont numérotées dans l’ordre de lecture, qui est l’ordre de l’arbre.*

La grammaire complète :

```
split := list
list  := item ( "/" item )*  |  item ( "|" item )*
item  := size [ "[" list "]" ]
size  := number ["%"] [ "~" number ["%"] ]  |  "*"  |  (nothing)
```

- **Les tailles** sont des pourcentages de la cellule parente selon l’axe de la liste ; `30` et `30%` reviennent au même. Un `*`, ou une taille vide, prend une part égale de ce que laissent les tailles écrites. Le dernier élément d’une liste va toujours jusqu’au bout, quelle que soit sa taille.
- **Un seul séparateur par liste.** Mélanger `/` et `|` dans la même liste, laisser un `[` non fermé ou placer une liste entre crochets sur l’axe de son parent est une erreur de syntaxe (`comicSplitSyntax`) : la partie lisible est utilisée.
- **Trop grand.** Des tailles dont la somme dépasse 100 % sont réduites de façon que chaque `*` garde 5 %, et la planche signale `comicSplitOverflow`.
- **L’ordre de lecture** est celui de l’arbre : les bandes de haut en bas et, dans une bande, les cases à partir du côté de départ. Les lignes `::panel` remplissent les cellules dans cet ordre. Une planche qui a plus de cases que de cellules laisse de côté les cases en trop ; une planche qui en a moins laisse les dernières cellules vides (`comicPanelCount`, signalé seulement quand un `split` est écrit).

Quelques découpages :

| Découpage | Planche |
| --- | --- |
| `*` | Une pleine page : une seule case. |
| `33 / 33 / *` | Trois bandes. |
| `* / * / * / *` | Quatre bandes égales, le yonkoma d’un gag en quatre cases. |
| `50 [* \| *] / 50 [* \| * \| *]` | Deux cases au-dessus de trois. |
| `30 / 35 [* \| * \| *] / *` | Une case large, trois cases sur une rangée, une case large. |
| `60 \| * [50 / *]` | Une case haute du côté de départ et deux cases empilées à côté. |
| `* [40~55 \| *] / 35` | Une bande d’action coupée par une ligne oblique, au-dessus d’une bande haute de 35 %. |

### Coupes obliques

Une taille écrite `a~b` donne une ligne oblique : le bord opposé de la cellule va de `a` % au début de l’axe transversal à `b` % à son autre extrémité. Pour des cases côte à côte, le début est le haut : `40~55 | *` trace donc une ligne qui part de 40 % de la largeur en haut et arrive à 55 % en bas ; pour des bandes, le début est le côté où commence la lecture. Les tailles s’additionnent séparément à chaque extrémité, et les cellules restent des polygones convexes. Les coins arrondis ne s’appliquent qu’aux cases rectangulaires ; une case oblique garde des coins vifs.

### Gouttières

La gouttière est à cheval sur chaque ligne de coupe, moitié d’un côté, moitié de l’autre : une ligne écrite à 30 % se trouve donc exactement à 30 % de sa cellule. Les lignes obliques gardent la gouttière de leur axe, mesurée perpendiculairement à la ligne. Les valeurs par défaut suivent la planche habituelle : 4 mm entre les bandes et 2 mm entre les cases d’une bande (`comics.gutter.horizontal` et `vertical`, ou l’attribut `gutter` de la planche).

### Fond perdu

Une case à fond perdu dépasse le cadre : chacun de ses côtés qui touche le cadre est repoussé jusqu’à la coupe, et jusqu’au fond perdu au-delà quand la page en a un (`page.cutLines.bleed`). L’attribut `bleed` de la planche met toutes les cases à fond perdu ; le `bleed` propre à une case ne s’applique qu’à elle, `bleed="top start"` seulement à ces côtés (`top`, `bottom`, `start`, `end`, `left`, `right`), et `bleed=false` l’annule. Un style de case avec `bleed: true` met ses cases à fond perdu.

### Retraits et incrustations

`pad` rend une case plus petite que sa cellule : une à quatre valeurs dans l’ordre des marges CSS avec des côtés logiques (haut, fin, bas, début), chacune une longueur ou un pourcentage de la cellule. `::panel{art=x pad="0 15%"}` donne une case plus étroite que sa bande, centrée dans celle-ci.

Une case avec `inset="x y w h"` ne prend pas de cellule : elle se superpose à la case précédente, à cette position et à cette taille en pourcentages du cadre de cette case, comme un petit portrait dans un coin ou un gros plan sur un plan large. Son script est lettré à l’intérieur.

```md
:::page{split="*"}
::panel{art=storm-wide}
caption{at=bottom-start}: The storm broke at midnight.
::panel{art=maya-window inset="62% 6% 32% 30%"}
maya{whisper}: Grandpa?
:::
```

## Cases et dessins

Une ligne `::panel{…}` ouvre une case ; son script va jusqu’à la ligne `::panel` suivante ou jusqu’à la clôture. Une case sans `art` est dessinée vide dans sa couleur de fond : c’est ainsi qu’on fait une case de texte seul. Le texte placé avant le premier `::panel` est lettré comme récitatif de la première case et signalé par `comicStrayText`.

| Attribut | Effet |
| --- | --- |
| `art=<id>` | Le dessin de la case : l’id d’une ressource bitmap ou SVG. Un id inconnu laisse la case vide et signale `comicUnknownArt`. |
| `fit=cover`, `fit=contain` | `cover` recadre le dessin pour remplir la cellule, sans jamais entamer sa zone de sécurité ; `contain` le montre en entier, avec des bandes de la couleur de fond de la case. Par défaut : celui du style de case (`cover`). |
| `focus="70% 40%"` | Le point du dessin à garder au centre là où la zone de sécurité laisse le choix. |
| `style=…` | Un style de case nommé pour cette case. |
| `border=none`, `border=0.5mm` | Pas de bordure, ou une bordure de cette épaisseur. |
| `bg=#1b1b2f` | Le fond de la case : une couleur, un id de palette ou `none`. |
| `bleed`, `bleed="top start"`, `bleed=false` | Voir [Fond perdu](https://postext.dev/fr/docs/comics.md#fond-perdu). |
| `pad="…"` | Voir [Retraits et incrustations](https://postext.dev/fr/docs/comics.md#retraits-et-incrustations). |
| `inset="x y w h"` | Superposer cette case à la précédente au lieu de lui donner une cellule. |
| `pop=<id>` | Une découpe dessinée par-dessus la bordure (voir [Dessins qui sortent de la case](https://postext.dev/fr/docs/comics.md#dessins-qui-sortent-de-la-case)). |
| `mirror`, `mirror=false` | Retourner le dessin de cette case, ou le garder tel qu’il a été dessiné quand la planche retourne ses dessins (voir [Sens de lecture](https://postext.dev/fr/docs/comics.md#sens-de-lecture)). |
| `alt="…"` | Le texte de remplacement de la case, pour le PDF balisé, le HTML et l’EPUB. Par défaut : l’`altText` de la ressource. |
| `id=…` | Un id pour la case, que les sorties HTML et EPUB utilisent comme ancre. |

### Recadrer le dessin dans sa cellule

Le dessin d’une case est fait sans tenir compte de la forme de la cellule où il finira : une planche peut être redécoupée, un séparateur déplacé, une traduction peut demander une bande plus haute. La **zone de sécurité** du dessin (`Resource.safeArea`, voir [Format du document › Zone de sécurité](https://postext.dev/fr/docs/document-format.md#zone-de-sécurité)) indique ce qui doit rester visible, et le recadrage est calculé pour chaque cellule :

1. Avec `fit=cover`, la fenêtre visible a les proportions de la cellule et elle est aussi grande que le dessin le permet.
2. Elle est placée de façon à contenir la zone de sécurité. Là où il reste de la marge, la fenêtre est centrée sur `focus` quand la case en définit un ; sinon, les marges hors de la zone de sécurité sont rognées en proportion de leurs tailles, de sorte qu’un sujet décalé vers la gauche reste décalé vers la gauche.
3. Si aucune fenêtre aux proportions de la cellule ne peut contenir la zone de sécurité (une cellule très large pour un sujet tout en hauteur), le dessin est montré avec sa zone de sécurité entière, et le reste de la cellule est rempli avec le fond de la case. La planche signale `comicPanelLetterbox`. Une cellule garde entière la zone de sécurité d’un dessin W × H tant que le rapport de sa largeur à sa hauteur reste entre `sw·W/H` et `W/(sh·H)`, où `sw` et `sh` sont la largeur et la hauteur de la zone de sécurité en fractions.

Un dessin sans zone de sécurité est recadré autour de son centre, ou autour de `focus`. Un SVG sans taille intrinsèque remplit la cellule tel quel. Les moteurs de rendu découpent selon le polygone de la cellule et dessinent l’image entière à sa taille non recadrée : le recadrage ne rééchantillonne donc jamais l’image.

### Styles de case

L’aspect d’une case vient de `comics.panel` (une bordure noire de 1 pt, un fond blanc, `cover`), d’un style nommé de `comics.panelStyles` qu’une planche ou une case choisit avec `style=`, et des `border` et `bg` propres à la case. Une bordure peut être `solid`, `none` ou `rough` : un trait à main levée qui tremble un peu, identique à chaque génération, puisque son tremblement est tiré d’une graine propre à la case. Voir [Configuration › Bande dessinée](https://postext.dev/fr/docs/configuration-comics.md#bande-dessinée) pour tous les champs.

### Dessins qui sortent de la case

Une bordure brisée, d’où un personnage ou un objet sort de sa case, demande deux dessins : le dessin de la case, découpé dans la cellule comme d’habitude, et une découpe transparente de la partie qui déborde, nommée avec `pop`. La découpe est recadrée et placée exactement comme le dessin (elle doit avoir la même taille que lui) et dessinée après la bordure, sans découpage, de sorte qu’elle couvre la bordure et la gouttière. Les découpes d’une planche sont dessinées après toutes ses cases : celle qui atteint la case voisine passe donc aussi par-dessus cette case, et sous toutes les bulles.

```md
::panel{art=gull-sky pop=gull-cutout bleed="top"}
sfx{at="70% 20%"}: SKRAAW
```

## Lettrage

Sous chaque ligne `::panel` vient le script de la case, une bulle par ligne :

```md
key{attributes}: text
```

La **clé** est un id de locuteur (lettres, chiffres, `_`, `.` et `-`), le même dans toutes les traductions ; elle correspond aux ancres des dessins et à la [distribution](https://postext.dev/fr/docs/comics.md#la-distribution). Trois clés sont réservées : `caption` (un récitatif), `sfx` (une onomatopée, sans bulle) et `note` (une note de l’éditeur). Les deux-points peuvent être le `：` pleine chasse que tape une méthode de saisie chinoise ou japonaise. Le texte est du Markdown en ligne : `*emphasis*` et `**strong**` s’impriment en gras italique, selon la convention du lettrage (en gras seul dans les écritures sans italique), `:tcy[12]` compose des chiffres en travers d’une colonne verticale, `:ruby` compose sa lecture en demi-corps au-dessus de sa base (à côté, dans une colonne), et `:ltr` / `:rtl` gardent un passage dans son propre sens à l’intérieur d’une bulle de l’autre sens. Une ligne en retrait de deux espaces ou d’une tabulation prolonge la bulle du dessus (jointe par une espace, sans espace entre deux caractères chinois ou japonais) ; une barre oblique inverse en fin de ligne force un saut de ligne à cet endroit dans le texte de la bulle. Les lignes vides sont ignorées, et une ligne qui n’est pas une ligne de script est lettrée comme récitatif et signalée par `comicStrayText`.

```md
::panel{art=lh-radio}
tomas: Just in time.
  The radio's gone deaf,\
  and there's a storm on the way.
biscuit{to="40% 70%"}: Mrrp?
note: Valve radios warm up for a minute before they speak.
```

Les attributs d’une ligne de script :

| Attribut | Effet |
| --- | --- |
| `{whisper}`, `style=whisper` | Le style de bulle. Un mot seul entre accolades nomme un style : `thought`, `whisper`, `shout`, `radio`, `inner`, ou l’un de ceux du livre. Un nom inconnu signale `comicUnknownBalloonStyle`, et la ligne prend le style de sa clé. |
| `at="62% 40%"` | Épingler le centre de la bulle à ce point du dessin de la case (de la cellule quand la case n’a pas de dessin). Le point est exprimé en fractions du dessin : il reste au même endroit du dessin malgré les recadrages et les déplacements de séparateurs. |
| `at=top-start` | Placer la bulle dans un coin ou contre un bord de la case : `top-start`, `top-end`, `bottom-start`, `bottom-end`, `top`, `bottom`. Les récitatifs et les notes sont collés contre la bordure. |
| `to="40% 70%"` | Diriger l’appendice vers ce point du dessin au lieu de l’ancre du locuteur. L’appendice se tourne vers lui et s’arrête un peu avant, comme devant une bouche. |
| `tail=none`, `tail=start` | Pas d’appendice, ou un appendice vers un locuteur hors champ de ce côté (`top`, `bottom`, `start`, `end`, `left`, `right`). `start` et `end` suivent le sens de lecture de la planche ; `left` et `right` sont des côtés du dessin de la case. `tail=auto` est la valeur par défaut. |
| `join`, `join=false` | Joindre cette bulle à la précédente du même locuteur, ou la garder séparée, quoi que dise `comics.lettering.joinSameSpeaker`. |
| `break` | Laisser la bulle franchir la bordure de la case vers la gouttière ou la case suivante. Ce franchissement n’est pas signalé comme un débordement, puisqu’il a été demandé ; ce que la bulle couvre de l’autre côté (un visage, la bulle d’une autre case) l’est toujours, et son centre reste dans sa propre case. |
| `rotate=-8` | Faire tourner la bulle ou l’onomatopée, en degrés, dans le sens des aiguilles d’une montre. |
| `skew=-8` | Incliner les lettres de ce nombre de degrés (positif vers l’avant, comme l’italique, négatif vers l’arrière), avant toute rotation : un texte écrit sur un objet vu en perspective, le titre sur une couverture de livre, une enseigne. Avec `rotate`, le lettrage suit à la fois le bord supérieur de l’objet et ses côtés. Un style de bulle l’applique à toutes ses lignes avec `skew`. |
| `size=1.5` | Mettre à l’échelle le lettrage d’une onomatopée. |
| `color=#c0392b` | La couleur du lettrage : une couleur hexadécimale ou un id de palette. |
| `font="…"` | Le caractère de cette ligne. |
| `vertical`, `horizontal`, `mode=vertical` | Le mode d’écriture de cette ligne, prioritaire sur celui du livre : `vertical` la compose en colonne dans une édition horizontale (un `ドン` non traduit), `horizontal` en ligne dans une édition verticale (une enseigne écrite en travers). Une colonne dans un livre d’une autre langue suit les règles de son propre texte (avec des kana, celles du japonais) et prend l’interlignage du lettrage vertical. Dans un livre vertical, une ligne sans caractères chinois ni japonais est de toute façon composée en ligne, sauf si elle indique `vertical`. |

### Comment une bulle est construite

Le lettrage suit les usages du lettrage de bande dessinée plutôt que ceux d’un bloc de texte :

- **Un seul corps par livre.** Toutes les bulles sont composées en `comics.lettering.fontFamily` au corps `comics.lettering.fontSize` (7,5 pt par défaut), mis à l’échelle seulement par le `fontScale` de leur style (un chuchotement à 0,9, un cri à 1,15). Le texte n’est jamais réduit pour tenir : une bulle qui ne tient pas change de forme, se déplace, franchit la bordure ou est signalée.
- **Le texte d’abord, la bulle ensuite.** Les mots sont d’abord répartis en lignes, en visant un losange : des lignes courtes en haut et en bas, la plus longue au milieu, pas de dernière ligne beaucoup plus courte que les autres, pas de ligne qui finit sur un article ou une préposition quand une autre coupure est aussi bonne. Puis le contour est tracé autour des lignes, avec le même blanc de tous les côtés.
- **Règles maison.** Trois points deviennent des points de suspension (ils restent trois points dans un caractère qui dessine ses points de suspension comme un seul point) ; `textTransform: 'uppercase'` compose le texte en capitales (les écritures sans casse ne sont pas touchées, et le ß allemand devient SS) ; en japonais et en chinois, le point qui termine une bulle est supprimé (`dropFinalStop`) et les `!` et `?` ASCII deviennent leurs formes pleine chasse dans les colonnes, deux d’entre eux dans une seule cellule (`!?` en ⁉) ; `doubleDash` lettre un tiret cadratin en `--`, comme le font les comics américains.
- **Placement.** Les bulles d’une case sont placées dans l’ordre de lecture à l’intérieur de la case, à `comics.lettering.inset` de sa bordure (1,5 mm). La première se place vers le haut, du côté de départ, et chaque suivante ne doit pas se trouver plus haut que la précédente, sauf si elle est plus avancée dans le sens de lecture. Les bulles évitent les visages et les zones que les dessins marquent, les bouches des locuteurs et les autres bulles ; elles se placent au-dessus ou à côté de leur locuteur plutôt qu’en dessous, et leurs appendices ne doivent pas se croiser. Un appendice ne traverse jamais le lettrage d’une autre bulle. Une bulle qui sort de sa case, avec `break` ou une épingle `at=`, évite ce que protègent les cases voisines comme elle évite ce que protège la sienne : leurs récitatifs et leurs bulles, les appendices de ces bulles, et les visages et zones à éviter de leurs dessins ; la bulle d’une case surmontée d’une incrustation évite l’incrustation. Les récitatifs et les notes placés dans un coin passent en premier, puis les bulles épinglées, puis les autres. Le placement est déterministe : la même planche donne toujours les mêmes bulles.
- **Sur la feuille.** Le lettrage ne dépasse jamais la coupe. Dans une case à fond perdu, les bulles restent dans la partie de la case située à l’intérieur du cadre, où la coupe ne peut pas les entamer ; seule une onomatopée épinglée avec `at=` ou marquée `break` peut déborder dans le fond perdu à côté de sa case.
- **Quand ça ne tient pas.** Une bulle qui ne peut pas être placée proprement peut d’abord franchir la bordure (seulement avec `break`), puis couvrir une zone à éviter (jamais un visage), puis prendre une forme plus large ou plus étroite. Une forme prise pour tenir ne sépare pas un mot chinois (`comics.lettering.strictPlacement`, activé par défaut) : un mot coupé sur deux lignes coûte plus cher qu’une bulle lue dans le désordre, donc la bulle se déplace d’abord. Une longue ligne dans une cellule étroite est composée à peu près à la largeur de la cellule et déborde dans la gouttière plutôt que de se dresser en colonne étroite sur le visage de son locuteur. Si la bulle chevauche encore quelque chose, elle est placée là où elle gêne le moins et la planche signale `comicBalloonOverflow`, en nommant la case et ce qui est couvert. Un franchissement demandé, avec `break` ou avec une épingle `at=`, n’est pas un de ces défauts : l’avertissement ne nomme alors que ce que la bulle couvre. Une ligne plus courte, une case plus grande ou une épingle `at=` règle le problème.

### Appendices

L’appendice d’une bulle de parole pointe vers la bouche du locuteur, les petites bulles d’une bulle de pensée vers la tête (voir [Locuteurs et ancres](https://postext.dev/fr/docs/comics.md#locuteurs-et-ancres)). Il quitte le contour face au locuteur, s’incurve un peu et s’arrête avant la bouche. Quand l’ancre du locuteur est rognée hors de la case, ou que le dessin ne marque aucune ancre pour ce locuteur, l’appendice pointe hors de la case, vers l’ancre ou vers la bordure la plus proche, et s’arrête sur la bordure. Une telle bulle se place près de la bordure d’où vient la voix, avec un appendice court : un appendice tracé à travers toute la case se lit comme une flèche, c’est donc la bulle qui se déplace plutôt que l’appendice qui s’allonge. `to="x% y%"` dirige un appendice vers un autre point du dessin, et `tail=start` (ou `top`, `bottom`, `end`, `left`, `right`) vers un locuteur hors champ de ce côté. Les récitatifs, les notes et les onomatopées n’ont pas d’appendice.

### Bulles jointes

Deux lignes à la suite du même locuteur sont jointes, comme on lettre la pause d’un personnage : avec `joinSameSpeaker: 'butt'` (la valeur par défaut), les deux corps se chevauchent et partagent un seul contour ; avec `'connector'`, un col étroit les relie ; avec `'none'`, elles restent deux bulles à deux appendices. Seule la première bulle d’un groupe a un appendice. Une ligne peut demander son propre comportement avec `join` ou `join=false`.

### Onomatopées

Une ligne `sfx` n’a pas de bulle : son texte est composé en grand (2,4 fois le corps du lettrage), en gras, dans le caractère d’onomatopées de la langue du document, avec un halo blanc autour des lettres pour qu’il se lise sur le dessin. Il est placé près de l’ancre `sfx` de la case quand le dessin en marque une (une ancre dont l’id est `sfx`), sinon là où il ne couvre aucun visage. `rotate`, `size`, `color` et `font` le modifient, et `at` l’épingle. En lettrage vertical, il est composé en colonne, de cinq caractères au plus. Les onomatopées sont peintes après toutes les bulles de la planche.

Une ligne peut fixer son propre mode d’écriture. Une édition horizontale d’un manga qui garde une onomatopée en japonais la compose en colonne :

```md
sfx{vertical size=2.6 font="Dela Gothic One"}: ドン
```

### Réglages du lettrage

`comics.lettering` contient le caractère, le corps, l’interlignage, la couleur et les règles maison de tout le livre. Par défaut, le caractère suit la langue du document, de même que le caractère des onomatopées :

| Langue | Lettrage | Onomatopées |
| --- | --- | --- |
| Latin, grec, cyrillique et les autres | Comic Neue | Bangers |
| Japonais | Zen Antique | Dela Gothic One |
| Chinois simplifié | Noto Sans SC | ZCOOL KuaiLe |
| Chinois traditionnel | LXGW WenKai TC | LXGW WenKai TC |
| Arabe, persan, ourdou | Playpen Sans Arabic | Lalezar |

Tous sont sur Google Fonts et se chargent par leur nom comme n’importe quel caractère. Avec la hauteur de ligne par défaut, les bulles verticales, chinoises ou japonaises sont composées avec un interlignage de 1,5 et les bulles arabes de 1,45. Le lettrage japonais, chinois et arabe n’est jamais incliné, quoi que dise le style, et l’arabe n’est jamais interlettré.

### La distribution

`comics.cast` énumère les personnages : un id (la clé du locuteur), un `name` et, en option, le style de bulle dans lequel il parle (`balloonStyle`, un robot qui parle toujours en `radio`), la `color` du texte et le `fill` de la bulle qui le distinguent, et une `fontFamily`. Le nom est ce que les sorties annoncent : la visionneuse HTML et l’EPUB recomposable impriment « Nom : paroles » pour chaque ligne. Un locuteur qui figure dans la distribution n’est jamais signalé comme inconnu.

```ts
comics: {
  cast: [
    { id: 'maya', name: 'Maya' },
    { id: 'tomas', name: 'Grandpa Tomás' },
    { id: 'beacon', name: 'The beacon', balloonStyle: 'radio', fill: { hex: '#e8f4ff', model: 'hex' } },
  ],
}
```

## Styles de bulles

Un style de bulle est un type de bulle : son contour, son appendice et son lettrage. Neuf sont prédéfinis, et une ligne de script en nomme un par un mot seul entre accolades (`ben{whisper}: …`). Les clés réservées prennent le leur d’après leur nom (`caption:`, `sfx:`, `note:`), et toutes les autres lignes prennent `speech`, ou le `balloonStyle` de leur locuteur dans la distribution.

| Style | Contour | Appendice | Par ailleurs |
| --- | --- | --- | --- |
| `speech` | ovale | courbe | La base de tous les styles : fond blanc, contour noir de 0,6 pt, 0,55 em de blanc. |
| `thought` | nuage | petites bulles | Les petites bulles pointent vers la tête, pas vers la bouche. |
| `whisper` | ovale, en tirets | courbe | Texte à 0,9. |
| `shout` | explosion | pointe | Texte à 1,15, en gras ; contour de 0,8 pt. |
| `radio` | électrique (zigzag) | zigzag | Une voix venue d’une radio, d’un téléphone ou d’une machine. |
| `caption` | rectangle | aucun | Jaune pâle, collé contre le coin haut du côté de départ, texte aligné au début. |
| `inner` | boîte arrondie | aucun | Fond gris, italique : la voix intérieure d’un personnage. |
| `note` | rectangle | aucun | Collé contre le coin bas du côté de fin, texte à 0,8. |
| `sfx` | aucun | aucun | Texte à 2,4, en gras, dans le caractère d’onomatopées, avec un halo blanc de 1,5 pt. |

`comics.balloonStyles` les modifie et en ajoute d’autres. Une entrée avec l’id d’un style prédéfini se superpose à ce style ; une entrée avec un nouvel id part de `speech` :

```ts
comics: {
  balloonStyles: [
    { id: 'caption', fill: { hex: '#ffffff', model: 'hex' }, textTransform: 'uppercase' },
    { id: 'shout', burstPoints: 18, burstDepth: 0.3 },
    { id: 'eerie', shape: 'wavy', stroke: { hex: '#5b4a8a', model: 'hex' }, italic: true },
    { id: 'writing', shape: 'none', tail: 'none', fontFamily: 'Caveat', color: { hex: '#3b2a1a', model: 'hex' } },
  ],
}
```

```md
ghost{eerie}: Leave this house…
caption{at=bottom-end}: Three streets away.
```

Les contours sont `oval` (une superellipse ajustée aux lignes, `roundness` 2,2), `rounded`, `rectangle`, `cloud` (des festons comptés d’après le périmètre), `burst` (`burstPoints` pointes, d’une profondeur `burstDepth`), `wavy`, `electric` et `none`. `dash` trace un contour en tirets, `double` un contour double, et `wobble` (0–1) le fait trembler comme à la main, toujours de la même façon pour la même bulle. Les appendices sont `curved`, `wedge`, `bubbles`, `zigzag` et `none`. Tous les champs, avec leur valeur par défaut, sont dans [Configuration › Bande dessinée](https://postext.dev/fr/docs/configuration-comics.md#bande-dessinée).

Les moteurs de rendu dessinent une bulle comme un lettreur l’encre : tous les contours d’un groupe sont d’abord tracés à deux fois leur épaisseur, puis remplis, puis le texte est composé. Deux corps joints et leur appendice forment donc une seule forme, sans trait entre eux.

## Locuteurs et ancres

Les appendices pointent vers les personnages, et les bulles évitent les visages, parce que les dessins indiquent où se trouvent les personnages. Une ressource bitmap ou SVG peut porter des **ancres** : un point par locuteur, écrit une seule fois, en fractions du dessin (de 0 à 1 depuis son coin supérieur gauche), comme la zone de sécurité.

```ts
const arrive: Resource = {
  id: 'lh-arrive', typeId: 'figure', kind: 'bitmap', createdAt: 0, updatedAt: 0,
  bitmap: { fileId: 'lh-arrive.jpg', format: 'jpeg', width: 1100, height: 733 },
  altText: 'A girl in a yellow raincoat walks up a coastal path towards a lighthouse.',
  safeArea: { x: 0.2, y: 0.08, width: 0.42, height: 0.82 },
  anchors: [
    { id: 'maya', x: 0.29, y: 0.52, head: { x: 0.27, y: 0.47 }, face: { x: 0.22, y: 0.42, width: 0.12, height: 0.16 } },
    { id: 'biscuit', x: 0.4, y: 0.68 },
  ],
  avoid: [{ x: 0.51, y: 0.09, width: 0.1, height: 0.43 }], // the lighthouse
  faces: [{ x: 0.7, y: 0.5, width: 0.06, height: 0.1 }], // a walker on the path, who says nothing
};
```

- `id` est la clé de locuteur qu’utilise le script (`maya: …`). Une ancre dont l’id est `sfx` place les onomatopées de la case.
- `x`, `y` est la bouche, vers laquelle pointe l’appendice d’une bulle de parole.
- `head` est le point vers lequel pointent les petites bulles d’une bulle de pensée. Par défaut : la bouche.
- `face` est un rectangle qu’aucune bulle ne couvre. Une ancre qui n’en a pas reçoit une zone de protection de quelques em au-dessus et autour de sa bouche, de sorte que les bulles évitent quand même un visage qui n’a pas été délimité.
- `avoid` (sur la ressource) énumère d’autres rectangles qu’aucune bulle ne couvre : une main, un objet clé, une enseigne. Sous la contrainte, une bulle peut couvrir une zone à éviter, jamais un visage.
- `faces` (sur la ressource) énumère, sous forme de rectangles, les visages des personnes qui ne parlent pas : un passant, la foule derrière les locuteurs. Chacun suit la règle du visage d’un locuteur. Aucune bulle ne le couvre, à aucun repli, et une bulle qui le couvre quand même (une bulle épinglée) est signalée par `comicBalloonOverflow` ; les cheveux au-dessus sont protégés ; aucun appendice ne le traverse, ni ne traverse le corps en dessous, quand un autre tracé existe. Une ancre d’où ne parle aucune ligne protège aussi son visage, mais elle reste un locuteur pour tout le reste (sa bouche est un point qu’aucune bulle ne touche, et elle est vérifiée par rapport à la zone de sécurité) : `faces` est la marque d’un simple figurant.

Comme les ancres sont des fractions du dessin, elles suivent le recadrage : le moteur reporte chacune sur la page à travers le recadrage et le retournement de la case, si bien qu’un déplacement de séparateur, un autre découpage ou une traduction n’éloignent jamais un appendice de son locuteur. Une ancre que le recadrage laisse hors de la case fait pointer l’appendice hors de la case, dans sa direction. La zone de sécurité protège les ancres de ce cas : une ancre située en dehors est signalée par `comicAnchorOutsideSafeArea`, une indication pour l’auteur, puisqu’un recadrage peut la couper. Une clé de locuteur qu’aucun dessin de la planche ne marque et qu’aucune entrée de la distribution ne nomme est signalée par `comicUnknownSpeaker`, ce qui repère en général un id mal orthographié ; les planches dont les dessins ne marquent aucune ancre ne signalent rien, de sorte qu’une BD peut être lettrée sans ancres.

Les ancres et les zones à éviter appartiennent au dessin et sont les mêmes dans toutes les langues. Dans le Sandbox, elles se marquent dans l’éditeur d’image du [panneau Ressources](https://postext.dev/fr/docs/sandbox.md#panneau-ressources) : le champ **Zone de sécurité et lettrage** ouvre une boîte de dialogue à trois modes, Zone de sécurité, Locuteurs et Zones à éviter. Un clic sur le dessin y ajoute un locuteur ; sa bouche, sa tête et son visage se mettent en place par glisser-déposer, et son id se tape à côté ; un glissement sur une partie vide du dessin trace une zone à éviter. Le mode Zones à éviter marque aussi les visages des personnes qui ne parlent pas : un bouton en ajoute un, et un interrupteur sur chaque ligne transforme une zone en visage de ce type ou l’inverse. Chaque marque se déplace avec les touches fléchées (Maj pour des pas plus grands) et se supprime avec Suppr, et la boîte de dialogue a son propre historique d’annulation.

## Strips

Un bloc `:::strip` a le corps d’une planche mais se place dans le texte, comme un encadré de la largeur d’une colonne ou de la page, découpé en cases : le strip quotidien d’un journal, un gag en quatre cases dans un magazine, une courte BD dans un livre de prose.

```md
:::strip{split="* | * | * | *" aspect=4 span=page placement=top}
::panel{art=pip-1}
pip: Otto, the ice is melting.
::panel{art=pip-2}
otto{thought}: Not the bakery…
::panel{art=pip-3}
::panel{art=pip-4}
sfx: SPLOSH
:::
```

| Attribut | Effet |
| --- | --- |
| `span=column`, `span=page` | Occupe la colonne où il tombe (par défaut) ou le bloc de texte. |
| `placement=here` | La valeur par défaut : composé dans le texte, là où il est écrit. Un strip n’est jamais scindé ; s’il ne tient pas, il passe en entier à la colonne ou à la page suivante, en emportant un titre placé juste au-dessus. Un strip sur toute la largeur de la page écrit `here` sur une page à plusieurs colonnes coupe les colonnes là où il se trouve, comme un encadré sur toute la largeur : le texte au-dessus finit à la même hauteur dans toutes les colonnes, le strip traverse la page, et le texte continue dans les colonnes en dessous. Quand le reste de la page ne peut pas le contenir, il ouvre la page suivante. |
| `placement=top`, `bottom`, `auto` | Flottant comme une figure, en haut ou en bas de la première page qui a de la place à partir de ce point. |
| `width=60%`, `width=12cm` | La largeur du strip : une part de la justification qu’il occupe (la colonne, ou le bloc de texte avec `span=page`) ou une longueur (un nombre seul est en millimètres). Jamais plus large que cette justification. Sans cet attribut, le strip prend toute la justification. |
| `align=start`, `center`, `end` | La position d’un strip plus étroit dans sa justification. La valeur par défaut est `center`. `start` et `end` suivent le sens du texte : le début est le bord droit sur une page de droite à gauche et le haut d’une colonne sur une page verticale. |
| `height=4cm` | La hauteur du strip (un nombre seul est en millimètres). |
| `aspect=4`, `aspect=4/1` | Le rapport largeur sur hauteur, la largeur étant celle du strip. Sans cet attribut, le strip prend les proportions qui rendent ses cases carrées (quatre cases sur une rangée : 4). |
| `caption="…"` | Une légende sous le strip (ou au-dessus, avec `captionStyle.position: 'above'`), composée à la largeur du strip dans le style de légende des figures. Le texte est du Markdown en ligne, écrit dans la langue de chaque édition. |
| `type=figure` | Numérote un strip légendé comme une ressource de ce type : la légende commence par le libellé et le numéro du type (« Figure 3. »), et le strip compte dans la séquence de ce type, dans l’ordre de lecture avec les ressources du type. Sans `type`, ou avec un type qu’aucun type de ressource ne porte, la légende est simple et le strip n’est pas compté. |
| `id=…` | Nomme un strip numéroté : `:ref{id="…"}` imprime son libellé et son numéro, comme pour une figure, et crée un lien vers lui. |

Un strip sans `split` place ses cases côte à côte. Le cadre du strip est sa boîte : les gouttières, les styles de case et le lettrage sont ceux de la configuration, et rien n’est à fond perdu. Un strip plus haut qu’une colonne est réduit à la hauteur de la colonne, légende comprise. Après lui, le texte reprend à la ligne suivante de la grille de ligne de base, avec l’écart que demande `layout.inlineResourceGap`, comme après une figure en ligne. Sur une page verticale, le strip est mis en page dans sa boîte tournée : un yonkoma `* / * / * / *` avec `aspect=0.25` descend donc le long d’une colonne verticale, et sa légende le suit comme une colonne de texte.

```md
:::strip{split="* | * | *" width=70% caption="Pip and Otto on the ice." type=figure id=pip-ice}
::panel{art=pip-1}
::panel{art=pip-2}
::panel{art=pip-3}
:::

As :ref{id="pip-ice"} shows, the ice was thin.
```

La légende reprend tout ce que fixe le style de légende : le caractère et le corps, les couleurs du libellé et du texte, la graisse et l’inclinaison du libellé, l’écart entre le strip et la légende, l’alignement et le filet derrière elle. Le `captionStyle` propre à un type s’applique aux strips numérotés dans ce type.

Dans le VDT, un strip est un bloc de `type: 'resource'` avec un champ `comic` et sans `resourceBlock` ; ses coordonnées sont relatives à la boîte du bloc sur la feuille, et un strip plus étroit, ou placé sous sa légende, se trouve à l’intérieur de cette boîte (`comic.frame` ne commence alors pas à son coin). Une légende correspond aux `lines` propres du bloc, composées dans les polices et les couleurs de la légende, et `stripCaption` indique de quelles lignes il s’agit, où elles se trouvent, le type et le numéro, et le filet. `pageComics(page)` donne la planche d’une page et les strips de ses colonnes et de ses flottants, tous reportés sur la feuille.

## Doubles pages

`:::page{spread}` étale un seul arbre de découpage sur deux pages en regard, pour un panorama ou une bataille qui demande tout le livre ouvert. Le cadre est fait des deux blocs de texte réunis par-dessus le dos, sans leurs marges intérieures ; le découpage, les gouttières et le recadrage fonctionnent comme sur une seule page, et une case ou un dessin peut passer par-dessus le dos.

```md
:::page{spread split="25 [* | * | *] / *" bleed}
::panel{art=market-1}
::panel{art=market-2}
::panel{art=market-3}
::panel{art=market-panorama}
caption: The Saturday market, from the clock tower.
:::
```

Une double page s’ouvre sur un verso, la page paire, pour que ses deux moitiés se fassent face : une page blanche est ajoutée avant elle quand la BD commencerait sur un recto, y compris au tout début d’un livre, où la page 1 est seule. Dans un livre relié à gauche, le verso est la page de gauche ; dans un livre relié à droite (arabe, japonais et chinois verticaux, BD lue de droite à gauche), c’est celle de droite. Chaque page porte sa moitié : les cases qui atteignent son côté, coupées au dos, le dessin d’une case qui passe par-dessus le dos étant dessiné sur les deux pages. Les cases à fond perdu débordent en tête, en pied et sur les bords extérieurs, jamais au dos. Rien n’est lettré dans une bande de 8 mm autour du dos, où la reliure l’avalerait : une bulle épinglée à cet endroit glisse vers le côté le plus proche. Chaque bulle se place sur la page qui contient son centre.

## Sens de lecture

Une bande dessinée occidentale se lit de gauche à droite : la première case d’une bande est à gauche. Le manga et la BD arabe se lisent de droite à gauche. Postext lit le sens dans `comics.readingDirection` :

- `'auto'` (la valeur par défaut) : le sens de la langue de l’édition. De droite à gauche dans un document de droite à gauche, de sorte qu’une édition arabe retourne ses planches ; dans un document vertical ; et en japonais et en chinois traditionnel (`ja`, `zh-Hant`, `zh-TW`, `zh-HK`, `zh-MO`), de sorte qu’une édition japonaise d’une BD occidentale se lit de droite à gauche, comme le manga. Dans toute autre langue, chinois simplifié et coréen compris (le manhua et le manhwa se lisent aujourd’hui de gauche à droite), c’est le sens pour lequel le dessin a été fait, `comics.artDirection` (`'ltr'` par défaut) : un manga, avec `artDirection: 'rtl'`, se lit de droite à gauche dans n’importe quelle langue.
- `'ltr'` ou `'rtl'` impose un sens à tout le livre, et l’attribut `direction` d’une planche l’impose à cette planche.

La reliure suit la bande dessinée. Avec `page.binding: 'auto'`, un livre dont la section `comics` se lit de droite à gauche est relié à droite : la page 1 est seule à gauche du dos, les doubles pages se lisent `[3 | 2]`, les visionneuses tournent les pages vers la gauche, le PDF demande un ordre des pages de droite à gauche et l’EPUB déclare une progression des pages de droite à gauche. La section `comics` décide pour tout le livre : un chapitre sans planches est donc relié comme les autres ; la `direction` propre à une planche ne déplace pas le dos. Un document dont les planches suivent les valeurs par défaut, sans section `comics`, garde la reliure de son texte (`comics: {}` suffit pour le relier selon le sens de lecture de ses planches). `page.binding: 'left'` ou `'right'` l’emporte toujours. Voir [Reliure](https://postext.dev/fr/docs/configuration-page-layout.md#reliure).

La même source donne les deux mises en page. Une planche de droite à gauche dispose les cases de chaque bande à partir de la droite : la case 1 se trouve en haut à droite ; les obliques s’inversent avec elles, de même que les côtés logiques de `bleed`, de `pad` et des mots-clés de coin (un récitatif en `top-start` se place en haut à droite). Les bandes gardent leur ordre de haut en bas, et l’ordre de lecture du script ne change pas. Le lettrage inverse aussi ses règles : la première bulle d’une case penche vers la droite, et la suivante ne peut se placer plus haut que plus loin vers la gauche.

Les dessins ne sont pas retournés sauf demande : `comics.mirrorArt: true` retourne chaque dessin d’une planche lue dans le sens opposé à `artDirection`, ce que les éditions occidentales du manga ont longtemps fait, et `::panel{mirror=false}` garde un dessin tel qu’il a été dessiné (un texte dessiné dans l’image, un escrimeur droitier). Les ancres et les zones de sécurité suivent le retournement. Un appendice hors champ dirigé avec `tail=left` ou `tail=right` aussi : ces valeurs nomment un côté du dessin, alors que `tail=start` et `tail=end` suivent le sens de lecture de la planche. Avec le dessin gardé tel quel, `tail=right` s’arrête sur la bordure droite dans toutes les éditions ; avec le dessin retourné, sur la bordure gauche. Mieux vaut en général laisser le retournement désactivé : l’ordre des cases suit déjà le lecteur, et un dessin retourné change ce que l’artiste a dessiné.

L’écriture des bulles suit la langue, pas l’ordre des cases. Avec `comics.lettering.writingMode: 'auto'`, le japonais et le chinois traditionnel sont lettrés en colonnes verticales lues de droite à gauche, de `maxColumnChars` caractères au plus (8), avec les règles kinsoku du japonais, les chiffres composés en travers de la colonne avec `:tcy` et la ponctuation dans ses formes verticales ; un document composé en écriture verticale est lettré verticalement dans n’importe quelle langue. Le chinois simplifié, le coréen et toutes les autres langues sont lettrés horizontalement, et `'horizontal'` ou `'vertical'` impose l’un ou l’autre. L’arabe est lettré de droite à gauche, avec les mots latins et les chiffres dans leur propre sens, jamais incliné et jamais interlettré.

## Traduire une bande dessinée

Une traduction est un fichier Markdown avec les mêmes lignes `:::page` et `::panel`, les mêmes clés de locuteur, et d’autres mots. Les dessins, leurs zones de sécurité, leurs ancres et leurs zones à éviter, les découpages et les épingles (les points `at=` sont en fractions des dessins) sont partagés par toutes les éditions. Chaque édition fait son propre lettrage : le texte est mesuré dans sa langue et son caractère, réparti en ses propres lignes, et chaque bulle est à nouveau dessinée et placée autour.

```md
:::page{split="30 [30 | 20 | *] / *"}
::panel{art=lh-arrive}
caption: Todos los veranos, Maya pasaba una semana en el faro.
maya: ¡Abuelo! ¡Ya estoy aquí!
::panel{art=lh-radio}
tomas: Llegas justo a tiempo. La radio se ha quedado sorda,
tomas: y viene tormenta.
:::
```

Ce qui change d’une édition à l’autre découle de la `locale` du document : les caractères par défaut du lettrage et des onomatopées, le lettrage vertical pour le japonais et le chinois traditionnel, les points finaux supprimés du chinois et du japonais, l’interlignage, et le sens des pages d’une édition arabe. Le texte est environ un cinquième plus long en espagnol ou en français qu’en anglais, et bien plus court en chinois ou en japonais ; les bulles grandissent et rétrécissent avec lui, et `comicBalloonOverflow` nomme les cases où une traduction demande une ligne plus courte ou une épingle. Les onomatopées sont des mots comme les autres : traduisez-les, ou gardez l’original et ajoutez-en une petite en dessous, comme un sous-titre.

## Sorties de la bande dessinée

Toutes les sorties peignent la même BD : les fonds des cases, les dessins découpés selon les cases, les bordures, les dessins qui sortent des cases, puis les bulles de chaque groupe joint (contours tracés, puis remplis, puis le texte), et les onomatopées en dernier.

- **Canvas.** Le canvas du Sandbox, les pages 3D du Folio et les vignettes peignent tous avec `renderPageToCanvas`.
- **PDF.** `postext-pdf` peint les planches avec des contours vectoriels et les caractères de lettrage incorporés, et les dessins SVG qui ont un original d’impression PDF (`svg.pdfFileId`) sont incorporés sous la forme de cet original. Un PDF balisé donne à chaque planche un `Div`, à chaque case un `Figure` avec son texte de remplacement (l’`alt` de la case, sinon l’`altText` de la ressource), et à chaque bulle un `P` après la figure de sa case, dans l’ordre de lecture ; une onomatopée est un `Span` dont le `/ActualText` est ses mots. Les fonds, les bordures et les contours sont des artefacts. La sortie passe les contrôles PDF/UA-1 de veraPDF.
- **HTML.** Chaque case est une `<figure>` qui contient un SVG en ligne (le découpage, le dessin, la bordure, la découpe) avec le texte de remplacement de la case, suivie de ses bulles sous forme d’éléments `<p>` dans l’ordre de lecture. Une bulle de parole commence par le nom du locuteur dans un span visuellement masqué : un lecteur d’écran entend donc « Maya : Grandpa ! I'm here ! » ; une onomatopée est annoncée comme une image dont le libellé est ses mots. La visionneuse HTML du Sandbox, dont les pages ont la largeur d’un écran et la hauteur d’un défilement, compose une planche comme la page imprimée mise à l’échelle de la fenêtre (`comics.viewerLeaf`), jamais recomposée.
- **EPUB à mise en page fixe.** Les pages sont celles du livre imprimé. Le livre porte aussi une **navigation par régions** (`regions.xhtml`, EPUB Region-Based Navigation 1.0) : une région `panel` par case, dans l’ordre de lecture, avec ses bulles, ses récitatifs et ses onomatopées imbriqués, de sorte qu’un système de lecture peut parcourir le livre case par case. La progression des pages suit le sens de lecture de la BD. L’option `kindlePanelView: true` de `renderToEpub` ajoute Kindle Panel View : une zone tactile par case et une copie agrandie qui remplit l’écran, avec les métadonnées que demande Kindle.
- **EPUB recomposable.** Une planche n’a pas de page à conserver ici : chaque case devient donc son dessin, recadré exactement comme sur la page, suivi de ses lignes sous forme de texte : « **Maya** : Grandpa ! I'm here ! », les récitatifs comme des paragraphes, les onomatopées en italique.

La légende d’un strip est peinte avec le texte, comme celle d’une figure. Dans un PDF balisé, c’est la `Caption` du `Div` du strip, lue après ses cases (avant elles quand elle est placée au-dessus du strip). Dans le HTML, l’EPUB à mise en page fixe et l’EPUB recomposable, le strip et sa légende forment une seule `<figure>`, la légende étant sa `<figcaption>` ; sur une page verticale ou de droite à gauche, où le dessin du strip est posé sur la feuille, le HTML garde la légende à part dans le texte.

## Modifier une bande dessinée dans le Sandbox

Dans le canvas, les pages HTML et le mode sélection du Folio, une planche se modifie au pointeur, et chaque modification est réécrite dans le Markdown du chapitre (le [guide du Sandbox](https://postext.dev/fr/docs/sandbox.md#modifier-une-bande-dessinée) donne les détails) :

- **Séparateurs.** Au-dessus d’une gouttière, le pointeur devient un curseur de redimensionnement. Le faire glisser déplace la ligne de coupe ; au relâchement, le nouveau pourcentage est écrit dans la valeur `split` de la planche (l’attribut est ajouté si la planche n’en a pas), et la planche est remise en page. Maj aimante par pas de 5 %, Alt ne déplace que l’extrémité la plus proche, ce qui incline la ligne, et aucune cellule ne devient plus étroite que 5 %. Pendant le glissement, une case dont le dessin serait montré entier avec des bandes est signalée. Chaque séparateur est aussi un séparateur focalisable : les touches fléchées le déplacent de 1 % (5 % avec Maj), Entrée écrit la position, Échap la rétablit.
- **Cases.** Une petite barre d’outils au-dessus d’une case la divise par une ligne horizontale ou verticale, en ajoutant après elle un `::panel` vide pour la nouvelle cellule, ou la fusionne avec la case suivante : la ligne de coupe disparaît, et la case fusionnée garde le dessin de la première case et les deux scripts.
- **Bulles.** Faire glisser une bulle l’épingle : sa ligne de script reçoit `at="x% y%"` en pourcentages du dessin de la case, un groupe joint se déplaçant d’un bloc. Pendant le déplacement, la bulle devient orange là où elle couvrirait un visage ou une zone à éviter, ou sortirait de sa case, et l’infobulle précise lequel. Les touches fléchées poussent une bulle focalisée de 1 %, et un double clic sur une bulle épinglée retire l’épingle.
- **Appendices.** Faire glisser la pointe d’un appendice écrit `to="x% y%"`, le point visé par l’appendice, dans la ligne ; un double clic sur la pointe le retire, et l’appendice pointe à nouveau vers son locuteur. Depuis une bulle focalisée, T passe à son appendice, dont les touches fléchées déplacent la cible.
- **Clavier.** Une case focalisée se divise avec H (une ligne horizontale) ou V (une ligne verticale) et se fusionne avec la suivante avec M.
- **Strips.** Tous les outils fonctionnent aussi sur les blocs `:::strip`, dans une colonne, flottants ou sur toute la largeur de la page, plus étroits que leur justification ou sous une légende, et écrivent dans la clôture propre au strip.
- **Dessins.** La zone de sécurité, les locuteurs et les zones à éviter d’un dessin se marquent dans le panneau Ressources (voir [Locuteurs et ancres](https://postext.dev/fr/docs/comics.md#locuteurs-et-ancres)).
- **Réglages.** Le panneau Maquette a un groupe **Bande dessinée** : sens de lecture, cadre et gouttières, case par défaut et styles de case, lettrage, et styles de bulles, chacun avec un aperçu.
- **Éditeur.** L’éditeur de texte colore les lignes `:::page`, `::panel` et les lignes de script, et le panneau Vérifications liste les avertissements de BD à leurs lignes.

## Avertissements de bande dessinée

Chaque avertissement nomme la ligne ou l’attribut qu’il concerne, et le panneau Vérifications du Sandbox l’y liste sous le titre de la première colonne.

| Avertissement | Cause | Correction |
| --- | --- | --- |
| Découpage des cases illisible `comicSplitSyntax` | Une valeur `split` ne peut pas être lue en entier : un caractère parasite, un `[` non fermé, `/` et `\|` mélangés dans une même liste, ou une liste entre crochets sur l’axe de son parent. La partie lisible est utilisée ; une valeur sans rien de lisible donne une seule case. | Corriger la valeur d’après la [grammaire](https://postext.dev/fr/docs/comics.md#découper-la-planche) : un seul type de séparateur par liste, et des crochets pour changer d’axe. |
| Tailles de cases au-delà de 100 % `comicSplitOverflow` | Les tailles d’une liste font plus de 100 % au total. Elles sont réduites, chaque `*` gardant 5 %. | Diminuer les tailles, ou remplacer l’une d’elles par `*`. |
| Cases et cellules ne correspondent pas `comicPanelCount` | La planche a plus de lignes `::panel` que son découpage n’a de cellules (les cases en trop sont laissées de côté), ou moins (les dernières cellules restent vides). | Ajouter ou retirer des cellules dans le découpage, ou des cases dans le script. Une ligne `::panel` vide donne volontairement une cellule vide. |
| Texte hors d’une ligne de script `comicStrayText` | Du texte avant la première ligne `::panel`, ou une ligne sans `key:` qui ne prolonge pas la ligne du dessus. Il est lettré comme récitatif. | Commencer la ligne par une clé (`caption:`, `maya:`), ou la mettre en retrait de deux espaces pour prolonger la bulle du dessus. |
| Style de bulle inconnu `comicUnknownBalloonStyle` | Une ligne nomme entre accolades un style que ni les styles prédéfinis ni `comics.balloonStyles` ne définissent, souvent une faute de frappe (`{wisper}`). La ligne prend le style de sa clé. | Corriger le nom, ou ajouter le style à `comics.balloonStyles`. |
| Dessin de case introuvable `comicUnknownArt` | Le `art` ou le `pop` d’une case ne nomme aucune ressource, ou une ressource qui n’est ni un bitmap ni un SVG. La case est dessinée vide. | Corriger l’id, ou ajouter le dessin aux ressources du livre. |
| Dessin montré en entier dans sa case `comicPanelLetterbox` | Les proportions de la cellule sont trop éloignées de celles du dessin : aucun recadrage de cette forme ne contient toute la zone de sécurité, le dessin est donc montré avec sa zone de sécurité entière, entre des bandes de la couleur de fond de la case. | Déplacer le séparateur pour donner d’autres proportions à la cellule (le Sandbox signale ces positions pendant le glissement), tracer une zone de sécurité plus petite, ou utiliser un autre dessin. |
| Point du locuteur hors de la zone de sécurité `comicAnchorOutsideSafeArea` | Une ancre d’un dessin se trouve hors de sa zone de sécurité : un recadrage peut donc couper le locuteur, et l’appendice pointerait alors hors de la case. | Agrandir la zone de sécurité pour y inclure la bouche, ou déplacer l’ancre. Rien à corriger si le locuteur doit parfois être hors champ. |
| La bulle ne tient pas `comicBalloonOverflow` | Après tous les replis, une bulle couvre encore un visage, une autre bulle, une zone à éviter ou une bouche, fait passer son appendice à travers une autre bulle, ou sort de sa case (un franchissement demandé avec `break` ou une épingle `at=` ne compte pas). L’avertissement nomme la case et ce qui est couvert. Pendant qu’une bulle est déplacée dans le Sandbox, le visage ou la zone à éviter qu’elle couvrirait, ou la bordure qu’elle dépasserait, apparaît avant le dépôt. | Raccourcir la ligne ou la couper en deux, donner plus de place à la case, épingler la bulle avec `at=`, ou la laisser franchir la bordure avec `break`. Le texte n’est jamais réduit. |
| Locuteur sans point `comicUnknownSpeaker` | Une clé de locuteur qu’aucun dessin de la planche ne marque d’une ancre et qu’aucune entrée de la distribution ne nomme, le plus souvent une clé mal orthographiée. Ses appendices pointent hors de la case. Signalé seulement sur les planches dont les dessins marquent des ancres ; à titre d’information. | Corriger la clé, marquer le locuteur sur le dessin, ou ajouter une entrée `comics.cast` pour un locuteur qui n’apparaît jamais. |

Les avertissements reviennent dans les `contentWarnings` du document, avec la plage source de la ligne ou de l’attribut concerné (voir [Configuration › Avertissements dans le document](https://postext.dev/fr/docs/configuration-programmatic-usage.md#avertissements-dans-le-document)).

Un réglage de BD qui accepte un mot parmi quelques-uns et en contient un autre, comme `shape: 'ovl'` dans un style de bulle ou `readingDirection: 'rlt'`, est lu à sa valeur par défaut et listé dans le panneau Vérifications comme **Valeur de réglage inconnue** (`unknownConfigValue`), avec la valeur utilisée par le moteur et le mot le plus proche. Les réglages vérifiés sont `readingDirection`, `artDirection`, le `borderStyle` et le `fit` d’un style de case, le `writingMode`, le `textTransform`, le `dropFinalStop` et le `joinSameSpeaker` du lettrage, et le `shape`, la `tail`, la `target`, la `position`, l’`align` et le `textTransform` d’un style de bulle.

## Depuis le code

Une BD ne demande rien de plus que `buildDocument` : les ressources portent les dessins avec leurs zones de sécurité, leurs ancres et leurs zones à éviter, et la configuration peut porter `comics`. Le lettrage mesure son texte avec les polices chargées : les caractères de lettrage doivent donc être chargés avant la mise en page, comme n’importe quel caractère. `comicFontFamilies(config, markdown)` liste les caractères dont les BD d’un document ont besoin (le caractère de lettrage, ceux de chaque style de bulle et de la distribution, et ceux que ses lignes de script nomment avec `font=`), et `markdownHasComics(markdown)` indique si un texte contient des planches ou des strips. Dans le PDF, ces caractères sont demandés au fournisseur de polices comme les autres.

La BD mise en page se trouve dans le VDT : `page.comic` (`VDTComicPage`) contient le cadre, le sens de lecture, les cases dans l’ordre de lecture (polygone, bordure, fond, recadrage du dessin en `art.source` et `art.box`), les séparateurs (avec la plage source de la valeur `split` dans laquelle ils écrivent) et les bulles (tracé du contour, blocs de texte, pointe de l’appendice, type, locuteur). La grammaire du découpage est exportée pour les éditeurs : `parseComicSplit`, `serializeComicSplit`, `moveComicSplitLine`, `splitComicCell` et `mergeComicCells` lisent et réécrivent une valeur `split`, en gardant ses `*` quand c’est possible. `comicArtCrop` et `comicCropFeasibleRange` calculent un recadrage, et `comicArtPointToPage` reporte un point d’un dessin sur la page.

## Limites connues

- **Arbres de découpage seulement.** Un découpage coupe chaque cellule en deux par une ligne qui la traverse : les mises en page où aucune ligne ne traverse toute la planche, comme un moulinet de quatre cases autour d’une cinquième, ne peuvent pas s’écrire. Les incrustations et les dessins qui sortent des cases couvrent l’essentiel de ce à quoi servent ces mises en page.
- **Formes des cases.** Les cases sont des polygones convexes ; les cases rondes, déchiquetées ou sans bordure en vignette, les dessins tournés ou agrandis et les traits de vitesse se dessinent dans les images, pas avec Postext.
- **Lettrage.** Le ruby au-dessus d’une base de droite à gauche est omis, et les autres annotations s’impriment en texte simple dans les bulles. Le placement se fait case par case : les appendices sont empêchés de se croiser par un coût, sans être acheminés l’un autour de l’autre, et l’enchaînement d’une case à la suivante n’est pas équilibré. Les longues lignes dans de très petites cellules sont signalées, pas résolues. La zone de protection d’un visage non délimité est dimensionnée d’après la bouche et le point de la tête (quelques em quand l’ancre n’a pas de tête) : dans un gros plan, elle peut protéger la bouche plus que le visage ; délimitez le visage.
- **Les strips** ne peuvent pas occuper un nombre de colonnes inférieur à la page (`columns=2` sur une page de trois), et ne sont lus qu’au premier niveau d’un chapitre (pas dans un encadré, un tableau ou une note de bas de page). Une citation dans la légende d’un strip s’imprime telle qu’elle est écrite.
- **Doubles pages.** Une case arrondie coupée au dos perd tous ses coins arrondis. Une BD de droite à gauche dans un livre relié à gauche s’ouvre quand même sur la page de gauche : sa première case tombe donc sur la seconde page de la double page.
- **Les dessins qui sortent des cases** prennent le recadrage du dessin de la case : la découpe doit donc avoir la même taille que le dessin.
- **Sorties.** Le texte de remplacement de secours d’une case sans `alt` ni `altText` est « Panel N », en anglais. La navigation par régions décrit les cases obliques par leurs boîtes englobantes. Kindle Panel View copie la BD d’une page une fois par case, ce qui alourdit le fichier, et n’a pas été vérifié dans Kindle Previewer.

## Sources

- Nate Piekos, [*Comic Book Grammar & Tradition*](https://blambot.com/pages/comic-book-grammar-tradition) (Blambot) : types de bulles, récitatifs, emphase, double tiret.
- John Roshell et Comicraft, [*Balloon Tales*](https://balloontales.com/creating-tails-and-joins/) : appendices et jonctions, et [la méthode des calques](https://balloontales.com/the-layer-method/), qui trace puis remplit.
- Todd Klein, [*Digital lettering for comics*](https://kleinletters.com/Blog/digital-lettering-for-comics/).
- David Kurlander, Tim Skelly et David Salesin, [*Comic Chat*](https://kurlander.net/DJ/Pubs/SIGGRAPH96.pdf), SIGGRAPH 96 : placement des bulles dans l’ordre de lecture.
- IDPF, [*EPUB Region-Based Navigation 1.0*](https://idpf.org/epub/renditions/region-nav/epub-region-nav.html), et Amazon KDP, [Kindle Panel View](https://kdp.amazon.com/en_US/help/topic/G9GSTY4LTRT39D4Z).
