# Arquitetura: o pipeline de layout

> A passada de posicionamento e suas rodadas: como o motor posiciona linhas, flutuantes, notas e colunas, e repete o processo até o layout se estabilizar

- Versão HTML: https://postext.dev/pt/docs/architecture-layout-pipeline
- Última atualização: 2026-10-16
- Tempo de leitura: 29 min
- Outros idiomas: [en](https://postext.dev/en/docs/architecture-layout-pipeline.md), [es](https://postext.dev/es/docs/architecture-layout-pipeline.md), [ca](https://postext.dev/ca/docs/architecture-layout-pipeline.md), [de](https://postext.dev/de/docs/architecture-layout-pipeline.md), [fr](https://postext.dev/fr/docs/architecture-layout-pipeline.md), [it](https://postext.dev/it/docs/architecture-layout-pipeline.md), [zh](https://postext.dev/zh/docs/architecture-layout-pipeline.md), [ja](https://postext.dev/ja/docs/architecture-layout-pipeline.md), [ar](https://postext.dev/ar/docs/architecture-layout-pipeline.md)

## Em poucas palavras

Esta página é para programadores que querem saber como o Postext posiciona as coisas na página. Ele percorre o texto do começo ao fim e coloca cada linha, imagem, tabela, nota e boxe no seu lugar. Quando um passo posterior muda algo anterior, como o número de página de uma referência, ele começa a passada de novo. Ele para quando nada mais se move. A página lista cada passo, o que ele decide e quantas vezes pode rodar.

## Pipeline de layout

Uma passada de posicionamento, rodada quantas vezes a diagramação precisar. Esse é o pipeline de layout inteiro.

A passada (`placeDocumentPass`, em `packages/postext/src/pipeline/build.ts`) percorre o conteúdo do primeiro bloco ao último. Para cada bloco, ela faz tudo o que é preciso para compô-lo: mede-o na largura da coluna em que ele está prestes a entrar, verifica se cabe, divide-o ou o passa adiante segundo as regras de quebra e de manter junto, oferece um espaço aos flutuantes que ele cita e devolve o fluxo à grade de linhas de base (veja [Sistema de ritmo vertical](https://postext.dev/pt/docs/architecture.md#sistema-de-ritmo-vertical)). Flutuantes, viúvas e a grade não têm passada própria: são resolvidos à medida que cada bloco cai.

Algumas decisões não podem ser tomadas dentro de um único percurso, porque dependem de onde o percurso vai parar: os números de página que um sumário imprime, a altura em que um boxe de página inteira corta as colunas acima dele, as linhas que ainda faltam a uma coluna curta. Para essas, `buildDocument` roda a passada de novo com *dicas* tiradas do resultado da passada anterior. As cinco etapas abaixo acompanham um bloco ao longo da passada; [O condutor das passadas](https://postext.dev/pt/docs/architecture-layout-pipeline.md#o-condutor-das-passadas) e [Balanceamento de colunas](https://postext.dev/pt/docs/architecture-layout-pipeline.md#balanceamento-de-colunas) tratam das repetições.

### Etapa 1: estruturação do conteúdo

- **Entrada:** o `PostextContent` bruto
- **Ação:** converter o markdown em blocos de conteúdo, resolver as referências a recursos contra `resources[]` por ID, tirar do fluxo as definições de notas de rodapé (`[^id]:`), numerar as suas chamadas na ordem de citação, numerar os títulos e planejar os flutuantes: quais recursos flutuam, e depois de qual bloco (`notes[]` não é lido)
- **Saída:** uma lista plana de blocos de conteúdo (tipados e com conteúdo, mas sem página nem coluna atribuída) e o plano de flutuantes
- **Roda:** no início de cada passada de posicionamento. A análise do markdown é lida do seu memo depois da primeira passada; o resto é calculado de novo

### Etapa 2: medição do texto

- **Entrada:** um bloco de conteúdo com texto e a largura da coluna em que ele está prestes a entrar
- **Ação:** medir as linhas do bloco nessa largura com o **módulo de medição** dedicado (`packages/postext/src/measure/`), que acrescenta hifenização, justificação, trechos ricos em linha e a quebra de linhas de Knuth-Plass sobre o Pretext. O resultado são as `VDTLine[]` do bloco e a sua altura total
- **Detalhe importante:** a medição fica em cache. `cachedMeasureBlock` / `cachedMeasureRichBlock` (em `measure/cache.ts`) usam como chave o texto, as fontes, a largura e todas as opções que afetam o layout, então medir de novo um parágrafo que não mudou é uma simples consulta a um mapa
- **Saída:** as linhas do bloco, com dimensões precisas em pixels
- **Roda:** quando o percurso chega ao bloco, não antes, porque a largura depende de onde o bloco cai: uma coluna ao lado de uma coluna lateral, as linhas ao lado de uma figura contornada pelo texto, as subcolunas de um grupo `:::columns`. Um bloco que passa para uma coluna de outra largura é medido de novo nessa largura

O módulo se divide claramente por responsabilidade: `plain.ts` mede trechos simples, `rich.ts` mede trechos mistos de negrito/itálico/fórmulas, `font.ts` monta as strings de fonte e cuida do ciclo de vida do cache, e `canvas.ts` encapsula as primitivas brutas de largura de texto do canvas. Um detalhe do ciclo de vida importa na prática: as larguras de glifos medidas com uma fonte substituta não podem sobreviver à chegada da fonte real. `canvas.ts` guarda suas larguras por string de fonte e uma geração de medidas; `evictFontFamilies` (chamada quando uma compilação descobre que as faces de uma família mudaram, por `watchFonts`, por `prepareFonts` e por `loadBundleFonts`) descarta as larguras dessas famílias e limpa o cache do Pretext, e um `MeasurementCache` preenchido numa geração anterior descarta os seus blocos nessas famílias na consulta seguinte. `clearMeasurementCache()` faz o mesmo para todas as famílias. As próprias faces são carregadas antes do layout por `fonts/prepare.ts` (`prepareFonts`), e `fonts/faces.ts` lê de volta as faces que um documento diagramado usou, para os avisos `fontFallback`.

Os parágrafos em chinês, japonês e coreano seguem outro caminho pelo mesmo módulo. Um parágrafo com mais caracteres CJK do que espaços entre palavras vai para o **compositor CJK** (`cjkCompose.ts`). Ele corta o texto em unidades (um caractere, um trecho de texto latino, um travessão duplo ou reticências, uma caixa atômica como um chip ou uma chamada de nota), mede cada unidade uma vez e preenche as linhas pelo primeiro encaixe, sob as regras de início e de fim de linha de `cjkClasses.ts`: uma linha absorve um sinal que não pode abrir a linha seguinte cedendo o branco da pontuação (`cjkPunctuation.ts`) antes de levar um caractere para baixo. Uma linha justificada é então espaçada entre seus caracteres. A saída são `VDTLine`s comuns, cujos segmentos carregam o que os renderizadores precisam para pintá-las exatamente como foram medidas: `tracking` (pixels depois de cada caractere, já incluídos na largura do segmento), `inkOffset` para um sinal que cedeu seu branco, `hangs`, os espaços entre han e latino como segmentos `autospace`, e os sinais, as leituras rubi e as linhas de warichu das anotações chinesas. Em um documento em japonês, o mesmo compositor segue o JLReq: as classes kinsoku dos níveis japoneses são lidas nos caracteres das bordas de cada unidade no momento da quebra, os brancos da pontuação são comprimidos e devolvidos na ordem do JLReq, os furiganas são diagramados por `rubyJis.ts` (espaçamento 1:2:1, avanço sobre o kana, palavras jukugo), e as marcas de kanbun alargam a unidade do seu caractere. O custo é linear no tamanho do parágrafo: o capítulo 1 de 紅樓夢 (6.949 caracteres) é composto medindo 1.298 caracteres, cada caractere distinto uma vez, onde medir prefixos do parágrafo custava 636.948. Veja [Composição em chinês](https://postext.dev/pt/docs/chinese-layout.md).

Por baixo, é aqui que o Pretext mostra seu valor. A chamada `prepare()` é a parte cara: ela analisa o texto com o mecanismo de fontes do canvas e guarda o resultado em cache. Já a chamada `layout()` é pura aritmética, quase de graça. Essa divisão é o que muda tudo. Depois que o texto foi preparado, o motor pode quebrá-lo de novo a um custo desprezível: em outra largura, para um parágrafo que continua em uma coluna mais estreita, ou com uma linha a mais, para um parágrafo que o balanceamento de colunas afrouxa. Prepare uma vez e diagrame quantas vezes precisar.

```typescript
// Simplificado: como o módulo de medição usa o pretext internamente
const prepared = prepare(paragraphText, '16px/1.5 Inter');
const { height } = layout(prepared, columnWidth, 24); // entrelinha de 24px
// => "Este parágrafo tem 168px de altura com 320px de largura de coluna, ou seja, 7 linhas."
```

### Etapa 3: posicionamento em páginas e colunas

- **Entrada:** um bloco medido
- **Ação:** compor o bloco na coluna atual. Criar os nós `VDTPage` e `VDTColumn` à medida que o fluxo precisa deles. Acompanhar a `availableHeight` de cada coluna. Quando um bloco não cabe, dividi-lo (Etapa 5) ou avançar para a coluna ou a página seguinte
- **Estratégia:** posicionamento guloso pelo primeiro encaixe. As quebras de coluna e de página seguem a atribuição válida mais simples
- **Saída:** o bloco tem `pageIndex`, `columnIndex` e `bbox` atribuídos

Este é o momento em que o VDT se torna um documento de verdade. Antes desta etapa, um bloco é uma entrada em uma lista plana, sem endereço. O posicionamento atribui cada um a uma página e a uma coluna, como quem despeja água em uma grade de recipientes: enche a coluna 1 até transbordar, derrama na coluna 2 e, quando a página está cheia, começa uma nova.

Antes que qualquer bloco de conteúdo entre em uma página, a passada reserva espaço para os elementos estruturais: cabeços e rodapés correntes (diagramados como espaços de design a partir de `config.header` / `config.footer`) e quaisquer faixas de flutuantes já pendentes para a página recém-aberta. Essas reservas reduzem a `availableHeight` de cada coluna, de modo que, quando os blocos de conteúdo começam a entrar, o motor já sabe exatamente quanto espaço há.

**Faixas de colunas e colunas de extensão.** `page.columns` é um array plano em ordem de leitura, mas uma página nem sempre é uma única fileira de colunas. Em uma diagramação de várias colunas, um bloco em linha de página inteira corta a página em *faixas* empilhadas. Quatro coisas contam como tal: um `:::callout` cujo estilo ou cuja cerca define `span: 'page'`, um grupo `:::columns{span="page"}` no texto corrido, um `::resource` em linha cujo posicionamento se resolve em `{ position: 'here', span: 'page' }` e uma tira de quadrinhos composta na largura da página no ponto em que está escrita (`:::strip{span=page}`). Os três últimos são envolvidos em um boxe de página inteira sem moldura antes do posicionamento (`pipeline/flowColumns.ts`, `pipeline/spanEmbeds.ts`), de modo que um único mecanismo compõe os quatro. No corte, as colunas de texto da faixa atual são fechadas na linha do corte (com a altura limitada e `availableHeight` zero), o bloco recebe sua própria coluna de largura total com `kind: 'span'`, e uma nova faixa de colunas de texto (`band + 1`, mesmo x e mesma largura, mesmo pé da faixa que substitui) é acrescentada abaixo dele. As colunas só são acrescentadas, nunca removidas, então `columnIndex` continua apontando para `page.columns[i]`, e os renderizadores não precisam de nenhum desenho especial: cada coluna recorta pela sua própria bbox (alargada por `columnClipRect`, com 2pt para a tinta dos glifos mais o quanto as sobreposições de design da coluna, como a aba de um título ou o selo de um boxe, passam das laterais, e mais o quanto o design de um título sobe acima do topo; o pé continua sendo a borda, o mesmo retângulo nos renderizadores de canvas e de PDF), e o fio entre colunas é desenhado por faixa entre colunas de texto vizinhas, começando abaixo da faixa que um título de página inteira ocupa no alto da página (`columnRuleSegments`), com o próprio fio da página quando uma seção estilizada define um (`VDTPage.columnRule`, lido por `pageColumnRule`). O balanceamento de colunas ignora as colunas de extensão, as colunas laterais e as faixas de altura zero. Um bloco de extensão corta diretamente onde a faixa está nivelada (no alto da página, logo depois de um título de abertura, de outro bloco de extensão ou de uma faixa de flutuante superior); quando chega a uma faixa desnivelada, ele propõe em vez disso um *limite de faixa* (`packages/postext/src/pipeline/bandCaps.ts`): as colunas da faixa que abre com um dado bloco de conteúdo são encurtadas para `ceil(Σ used / N / grid)` linhas, e `buildDocument` roda de novo a passada de posicionamento com o limite (aumentando-o uma linha por vez quando a faixa limitada transborda, até `MAX_BAND_CAP_RETRIES` = 3 vezes, e depois descartando-o, de modo que o bloco vai para o alto da página seguinte). Assim o texto preenche as colunas encurtadas sob todas as regras de posicionamento, termina nivelado no corte, e as colunas fechadas guardam a folga que restar como `availableHeight` para o balanceamento absorver. O mesmo mecanismo nivela a faixa de *fechamento* de um capítulo e do documento (`headings.balancing.trailing`): uma abertura de capítulo, um `:::part`, um boxe `placement: 'fixed'` que fecha o capítulo ou o fim do documento, alcançados com as colunas da faixa atual desniveladas, propõem um limite `kind: 'trailing'`, identificado pelo bloco de fronteira. Como um limite é identificado pelo bloco que abre sua faixa (e esse bloco se move sempre que uma página anterior absorve linhas extras de balanceamento), os limites de fechamento são resolvidos *depois* que o balanceamento de colunas se estabiliza, com as dicas de balanceamento congeladas, e uma rodada curta de polimento deixa então que as alavancas preencham o que o corte deixou curto. Os boxes `placement: 'fixed'` saem do fluxo: a caixa é ancorada à área de conteúdo da página, à caixa de refile ou à caixa de sangria, as colunas de texto que ela cobre cedem essa zona (cortada por baixo ou por cima, como uma faixa de flutuante, passando para a página seguinte em caso de conflito), e a moldura e os filhos vão para `page.floats`.

**Páginas verticais.** Com `layout.writingMode: 'vertical-rl'`, a passada diagrama uma página horizontal girada um quarto de volta no sentido horário. Nessa página, a área de conteúdo, as colunas, os blocos, as linhas, os flutuantes e as áreas de notas de rodapé estão em **coordenadas de fluxo**, e `VDTPage.flow` carrega a rotação que os leva até a folha: um ponto de fluxo (x, y) cai em (largura da página − y, x). Nada mais adiante precisa saber disso: quebras, flutuantes, regras de manter junto e balanceamento trabalham no referencial do fluxo como em qualquer página, uma coluna do fluxo é uma fileira na folha, e cada renderizador aplica a rotação ao pintar (`flowToPage` e `pageToFlow` convertem pontos nos dois sentidos). Cabeços, fólios, marcas de corte e o fundo ficam em coordenadas da folha. Figuras e tabelas são diagramadas como blocos em pé, girados de volta dentro do referencial, e os caracteres que ficam em pé são girados de volta um a um na hora de pintar.

### Etapa 4: posicionamento de recursos

- **Entrada:** o plano de flutuantes da Etapa 1 e as páginas como posicionadas até ali
- **Ação:** flutuar cada recurso referenciado para o primeiro espaço livre depois da sua primeira referência: o pé da coluna que o referencia, o alto / pé da próxima coluna vazia ou uma faixa da página seguinte (`packages/postext/src/pipeline/floatPlacement.ts` planeja os flutuantes, `pipeline/floatSlots.ts` enumera e mede os espaços; a passada de posicionamento reserva as faixas à medida que avança)
- **Roda:** assim que o bloco que contém a primeira referência cai, dentro do mesmo percurso; o texto posicionado depois disso corre ao redor da faixa
- **Resolução do posicionamento:** para cada recurso, o motor resolve `resource.placement` → o `resourceType.defaultPlacement` do tipo → o padrão embutido `{ position: 'auto', span: 'column' }`

| Campo de posicionamento | Comportamento |
| --- | --- |
| `position: 'auto'` | O recurso ocupa o primeiro espaço livre depois da sua referência, no alto ou no pé; é o padrão |
| `position: 'top'` | Só espaços no alto: uma faixa no alto da próxima coluna ou página vazia, empurrando o conteúdo da coluna para baixo dela |
| `position: 'bottom'` | Só espaços no pé: uma faixa no pé de uma coluna ou página, encurtando a coluna acima dela |
| `position: 'here'` | Fica fora da flutuação: o recurso é inserido no lugar da sua diretiva `::resource`, exatamente onde aparece no fluxo |
| `span: 'column'` | A faixa ocupa uma única coluna (em uma página recém-aberta, o motor escolhe a coluna com mais espaço sobrando) |
| `span: 'page'` | A faixa ocupa toda a largura do conteúdo, atravessando todas as colunas e interrompendo o fluxo das colunas; as faixas de largura total são reservadas primeiro, então os flutuantes de uma coluna se acomodam no espaço que sobra |
| `span: 'side'` | O recurso é composto na coluna lateral de um layout de coluna e meia que reserva essa coluna para flutuantes (`layout.sideColumnRole: 'floats'`), empilhado ao lado do parágrafo que o cita primeiro. Em uma página sem essa coluna, comporta-se como `'column'` |

`ResourcePlacement` tem mais campos além desses dois: `rotate`, `width`, `align`, `columns`, `captionSide`, `shrink`, `minScale`, `captionMeasure`, `wrap`, `wrapGap` e `citingPage`. [Configuração → Tipos de recurso](https://postext.dev/pt/docs/configuration-resources.md#tipos-de-recurso) e [Formato do documento › Posicionamento](https://postext.dev/pt/docs/document-format-resources.md#posicionamento) dizem o que cada um faz.

- **Posicionamento adiado:** um flutuante que não cabe em nenhum espaço da página atual espera pela próxima página que o fluxo abrir e, em uma fronteira de capítulo, é escoado para páginas abertas antes da fronteira. Uma imagem mantém o seu tamanho, a menos que `placement.shrink` (ou `layout.floatShrink`, para o documento inteiro) permita reduzi-la para caber em um espaço. Uma tabela alta demais para uma página nova é cortada entre linhas e continua nas páginas seguintes, com as linhas de cabeçalho repetidas (`tableStyle.overflow: 'split'`, o padrão)
- **Saída:** recursos posicionados em faixas da página (`page.floats`), com a altura das colunas afetadas reduzida para que o texto corra ao redor das faixas

O posicionamento de recursos é onde a coisa fica interessante, porque os flutuantes não apenas ocupam espaço: eles remodelam o espaço ao redor. Depois que o bloco que contém uma referência foi posicionado, o flutuante pendente recebe a oferta dos espaços livres da página atual depois dele: o pé dessa coluna, depois o alto e o pé da próxima coluna vazia (para um flutuante de página inteira, o pé da página, quando todas as colunas ainda têm espaço); o que não cabe em lugar nenhum espera pela página seguinte, onde os flutuantes pendentes ocupam suas faixas antes que qualquer texto entre. As colunas encolhem para caber entre as faixas, o texto corre sem interrupção pelas colunas estreitadas, e o leitor vê a figura perto (mas não exatamente no ponto) de onde ela é mencionada. É a prática comum da composição profissional; os livros fazem isso o tempo todo.

**Regras de posicionamento.** Além da escolha do posicionamento, os flutuantes seguem restrições editoriais estritas:

- **Regra de depois da referência.** Um flutuante cai no primeiro espaço livre *depois* da sua primeira referência no texto, nunca antes. O leitor encontra primeiro a referência e depois vê o recurso. Se um flutuante não couber, ele é adiado para um espaço ou página posterior, nunca para trás.
- **Ordem de referência dentro de uma sequência.** Cada espaço é oferecido aos flutuantes pendentes na ordem da primeira referência, e um que não cabe em lugar nenhum segura os que vêm atrás dele na mesma sequência de numeração: a tabela 3 nunca cai depois da tabela 4, a figura 12 nunca antes da figura 11. As sequências não se seguram umas às outras: uma tabela em espera deixa passar uma figura posterior. Para que uma tabela longa não precise esperar por uma página nova, uma tabela que recebe a oferta do alto de uma coluna vazia é cortada nessa coluna e continua no espaço seguinte (a coluna ao lado ou as faixas da página seguinte), com as linhas de cabeçalho repetidas.
- **Barreira de capítulo.** Os flutuantes nunca escapam do seu capítulo. Em uma abertura de capítulo (um nível de título com `breakBefore` ou `span: 'page'`), em um `:::part`, em um estilo de boxe com `floatBarrier: true` e no fim do documento, todo flutuante pendente é posicionado primeiro (nos espaços livres da página, depois em páginas abertas antes da fronteira, cada uma forçando o posicionamento de pelo menos um flutuante), antes da quebra de página da própria fronteira. Antes de abrir essa página, uma figura ou tabela ainda pendente recebe mais uma vez a oferta dos espaços livres da página atual, seja qual for seu `position`: um flutuante de alto de página citado na página de fechamento de um capítulo ocupa o pé dessa página, abaixo das colunas balanceadas, em vez de uma página só para ele (os boxes flutuantes mantêm seu posicionamento). Um `:::pagebreak` envia os flutuantes pendentes para a página que vem depois dele, após qualquer página de preenchimento por paridade.
- **Espaço mínimo de texto.** Em uma página recém-aberta, uma faixa só é reservada se ainda couberem pelo menos 3 linhas de texto corrido nas colunas afetadas, com uma exceção: um flutuante grande demais pode ser forçado em uma faixa que ainda seja só de texto, para que uma figura dominante não trave a fila para sempre. Um espaço na página atual precisa caber na altura restante da coluna; ali a regra das 3 linhas só vale ao lado de outra faixa de flutuante.
- **Respiro.** Um espaço da altura de uma linha do corpo separa uma faixa do texto ao lado.
- **Alinhamento à grade de linhas de base.** As faixas superiores são arredondadas *para cima* até um múltiplo da grade de linhas de base (aumentando o espaço abaixo do flutuante), para que toda linha deslocada continue caindo na grade. Os flutuantes inferiores são ancorados de modo que a última linha de base da legenda fique na grade: a legenda compartilha a linha de base com a última linha de texto das colunas vizinhas, e as páginas terminam na mesma altura entre colunas e entre páginas opostas.

**Numeração tipada pela primeira referência.** Os recursos *não* são numerados no markdown. `pipeline/resourceNumbering.ts` atribui a cada recurso seu número na primeira vez em que ele é referenciado na ordem de leitura, usando o `ResourceType` do recurso: o `numberingTemplate` combina o contador por tipo `{n}` com os contadores de títulos `{h1}`..`{h6}` vigentes na referência (por exemplo, `'{h1}.{n}'` → “1.7”), `resetOn` controla quando o contador recomeça (`'never'` ou em qualquer nível de título), e `counterFormat` escolhe numerais decimais, romanos ou alfabéticos. Os tipos embutidos *Figura* e *Tabela* vêm de `defaultResourceTypes(locale)`, localizados para o idioma do documento. Como a numeração segue a ordem da primeira referência, inserir uma figura nova no meio do documento renumera automaticamente tudo o que vem depois, sem nenhuma edição na fonte.

### Etapa 5: quebras e regras de manter junto

É esta etapa que separa um motor de layout de um despejador de texto. Ela aplica as regras de qualidade editorial que os compositores profissionais aplicam à mão há séculos, e que a renderização ingênua de texto ignora por completo.

Três mecanismos dividem o trabalho, cada um atuando onde existe a informação de que precisa: uma **penalidade de *runt*** dentro do quebrador de linhas, uma **divisão pontuada** onde um parágrafo encontra o fim de uma coluna e as **regras estruturais de coesão** entre blocos.

#### Viúvas, órfãs e *runts*

**Viúvas e órfãs** são os sinais mais visíveis de uma composição amadora:

- Uma **viúva** é uma única linha de um parágrafo deixada sozinha no pé de uma coluna. O parágrafo
  continua na coluna seguinte, mas essa linha solitária parece abandonada (como se a coluna tivesse
  terminado antes da hora).
- Uma **órfã** é uma única linha de um parágrafo isolada no topo de uma coluna. A maior parte do
  parágrafo está na coluna anterior, mas uma linha transbordou (e parece desligada do seu
  contexto).
- Um ***runt*** é um parágrafo cuja última linha é uma única palavra curta (ou duas): curta demais para parecer uma linha de texto de verdade. É menos grave estruturalmente que uma viúva, mas incomoda tanto quanto a um leitor atento.

**Os *runts* são resolvidos no quebrador de linhas.** Um *runt* pertence só ao parágrafo: a última linha é igualmente curta onde quer que o parágrafo caia. Por isso, das três, é a única regra que o quebrador de linhas de Knuth-Plass enxerga. Quando um conjunto de quebras terminaria o parágrafo em uma linha cujo conteúdo é mais estreito que `runtMinCharacters × normalSpaceWidth` (`runtMinCharacters` padrão 20: conteúdo com a largura de vinte espaços entre palavras, cerca de 8 a 12 letras), o quebrador soma `runtPenalty` (padrão 1000) à *badness* dessa linha dentro da fórmula quadrática de deméritos, de modo que ela concorre na mesma escala que a *badness* da linha (que satura em 10.000) em vez de ficar ofuscada por ela. A penalidade fica ao lado dos deméritos de sempre (a *badness*, o custo de hifenização e a incompatibilidade de classe de ajuste) em uma única otimização global, e o quebrador quase sempre encontra um conjunto de quebras sem *runt*. Quando não encontra, `bodyText.tightenRunts` compõe o parágrafo com uma linha a menos, se os espaços entre palavras, e um pouco de tracking negativo, conseguirem absorver a diferença. O módulo de medição entrega a penalidade ao quebrador (`measure/plain.ts`, `measure/rich.ts`), e `knuthPlass/breakpoints.ts` a aplica.

**Viúvas e órfãs são resolvidas no fim da coluna.** O quebrador de linhas quebra um parágrafo para uma largura. Ele não sabe em que ponto da página o parágrafo vai ficar, então não tem como saber quais das suas linhas vão fechar uma coluna. A passada de posicionamento sabe. Quando as linhas de um parágrafo não cabem todas no que resta da coluna, `chooseParagraphSplit` (em `pipeline/orphanWidow.ts`) testa cada divisão `k` das linhas já quebradas, de 0 até o número de linhas que cabem: `k` linhas ficam e as demais abrem a coluna seguinte. Cada candidata recebe uma pontuação:

- `slackWeight · slack²` pelas linhas que ela deixa vazias no pé da coluna, sendo `slack` as linhas que cabem menos `k` (`slackWeight` padrão 10);
- mais `widowPenalty` (padrão 1000) quando deixa menos de `widowMinLines` linhas no pé da coluna;
- mais `orphanPenalty` (padrão 1000) quando leva menos de `orphanMinLines` linhas para o topo da seguinte.

Vence a divisão com a menor pontuação, e `k = 0` passa o parágrafo inteiro. As regras são brandas, e os números dizem quanto: uma penalidade de 1000 é o custo de folga de dez linhas vazias (10 · 10² = 1000), então, com os padrões, uma divisão que respeita as duas regras vence sempre que deixa vazias menos de dez linhas da coluna. Além disso, uma viúva ou uma órfã pode ser a escolha mais barata. A divisão escolhe entre as linhas que o quebrador de linhas já fez; ela não quebra o parágrafo de novo. Os itens de lista passam pela mesma divisão com `avoidOrphansInLists` e `avoidWidowsInLists`, e recebem a penalidade de *runt* com `avoidRuntsInLists` (todos `true` por padrão).

Tudo isso é ajustável em `BodyTextConfig`; veja [Configuração → Órfãs, viúvas, runts e regras de coesão](https://postext.dev/pt/docs/configuration-page-flow.md#órfãs-viúvas-linhas-curtas-e-regras-de-manter-junto). Definir qualquer `*Penalty` como `0` desativa, na prática, a regra correspondente.

#### Regras estruturais de coesão (keep-together)

Alguns agrupamentos são maiores que um parágrafo: abrangem blocos adjacentes e não podem ser resolvidos só pela quebra de linhas. A passada de posicionamento os impõe à medida que cada bloco cai, empurrando grupos inteiros para a frente quando eles, de outro modo, ficariam divididos por uma quebra de coluna ou de página:

- **Título com o seu primeiro parágrafo.** Um título nunca deve aparecer no pé de uma coluna se o parágrafo que ele introduz começar na coluna seguinte. Imposto por `headings.keepWithNext` (padrão `true`): se não houver espaço para o título *mais* o mínimo de viúva do corpo (`bodyText.widowMinLines`, padrão `2`) do bloco seguinte, ou apenas uma linha quando `avoidWidows` está desligado, o título é empurrado para a frente para acompanhar o seu texto.
- **Títulos consecutivos.** Quando vários títulos aparecem em sequência (por exemplo, um h2 seguido de um h3 seguido de um parágrafo), o grupo inteiro deve permanecer junto. Nenhum dos títulos pode ficar isolado no pé de uma coluna sem o conteúdo que introduz.
- **Listas introduzidas por dois-pontos.** Quando um parágrafo termina com dois-pontos que introduzem diretamente uma lista, a linha com os dois-pontos deve ficar junto do início da lista. Imposto por `bodyText.keepColonWithList` (padrão `true`): se colocar o parágrafo não deixar espaço para que o primeiro item da lista comece (o item inteiro quando as regras de órfãs e viúvas para listas o mantêm inteiro; uma linha com `bodyText.colonListRoom: 'line'`, como até o postext 1.4), a última linha com os dois-pontos (ou o parágrafo inteiro, se ele tiver uma só linha) avança junto com a lista. Sempre que essa regra precisa empurrar o parágrafo inteiro e uma sequência de títulos o precede imediatamente na coluna, esses títulos também são puxados para a frente, para que `keepWithNext` não seja violado em silêncio; a única exceção é quando a coluna contém apenas os títulos que um movimento anterior já levou para a frente: nesse caso o motor mantém o parágrafo com o título e aceita a separação mais branda entre dois-pontos e lista para não entrar em laço.
- **Figura com a sua legenda.** Uma figura e a sua legenda são uma unidade inseparável. Elas sempre se movem juntas.

Quando detecta uma violação de coesão, o motor empurra o grupo inteiro para a coluna ou página seguinte. A coluna que ele deixou termina então curta, pelas linhas que o grupo teria ocupado. Preenchê-la é o trabalho do [Balanceamento de colunas](https://postext.dev/pt/docs/architecture-layout-pipeline.md#balanceamento-de-colunas).

Essas correções são invisíveis quando bem feitas (o leitor nunca deveria percebê-las). Mas a *ausência* delas salta aos olhos de quem lê com atenção: aquela linha solitária e desajeitada no topo de uma coluna, aqueles vãos irregulares onde o motor desistiu de fazer o texto caber. Editoras profissionais têm guias de estilo inteiros dedicados a evitar exatamente esses problemas. O Postext os automatiza.

### O condutor das passadas

> **Figura: A passada de posicionamento e as suas repetições**
> Uma passada de posicionamento leva o conteúdo por cinco etapas: estruturar, medir, posicionar, flutuantes, e quebras e regras de manter junto. buildDocument roda a passada inteira de novo com dicas da passada anterior: um limite de faixa, alavancas de balanceamento, o corte de uma faixa de fechamento, rótulos de página ou números de nota já definidos.
>
> *Uma passada posiciona o documento inteiro. buildDocument a roda de novo com dicas até que cada rodada se acomode ou chegue ao seu limite.*

`buildDocument` é um condutor em torno da passada de posicionamento (`buildDocumentGen` e `buildDocumentBalanced`, em `pipeline/build.ts`). Ele roda a passada uma vez. Depois lê o que a passada informou e, onde uma decisão pede um segundo olhar, roda a passada de novo, inteira, com dicas (`PassHints`): os limites, as linhas extras e os números que a passada seguinte deve aplicar. Quando nenhuma das condições da tabela abaixo aparece, essa primeira passada é a compilação inteira.

As repetições vêm em rodadas, cada uma para um tipo de decisão, nesta ordem:

| Rodada | Roda quando | A passada seguinte recebe | Limite |
| --- | --- | --- | --- |
| **1. Sumário** | O documento imprime números de página que o hospedeiro não forneceu: um `:::toc` ou um `:::index` sem `outline` no conteúdo, ou uma referência que imprime a página de uma âncora | A estrutura do livro com os rótulos de página da compilação anterior | `MAX_TOC_ROUNDS`: 3 compilações além da primeira |
| **2. Números das notas** | `footnotes.numbering` é `'page'`, `'column'` ou `'spread'` e as notas ficam no pé da coluna ou da página dupla, de modo que o número de uma nota depende de onde a diagramação a põe | O número que cada nota recebeu na compilação anterior (`footnoteNumbers`) | `MAX_FOOTNOTE_ROUNDS`: 3 compilações além da primeira |
| **3. Legendas laterais** | Um boxe lateral ou um flutuante lateral passou por cima da legenda que uma figura compôs na coluna lateral (`placement.captionSide`) | Essas legendas vão para baixo das suas figuras (`captionUnder`) | 3 passadas |
| **4. Flutuantes na página da citação** | Um flutuante com `placement.citingPage` caiu em uma página ou coluna posterior à linha que o cita | Essa página, ou coluna, abre com o flutuante no alto (`citingPageFloats`) | `MAX_CITING_PAGE_ROUNDS`: 3 rodadas de propostas e, depois, uma passada para cada lote de flutuantes que uma passada não conseguiu manter ali |
| **5. Limites de faixa** | Um bloco de página inteira chegou a uma faixa cujas colunas estão desniveladas (veja a [Etapa 3](https://postext.dev/pt/docs/architecture-layout-pipeline.md#etapa-3-posicionamento-em-páginas-e-colunas)) | A altura em que essa faixa deve ser cortada (`bandCaps`) | `MAX_BAND_PASSES`: 4 passadas de propostas e novas tentativas. Um limite cresce uma linha até 3 vezes (`MAX_BAND_CAP_RETRIES`) e depois é descartado |
| **6. Balanceamento de colunas** | `headings.balancing.enabled` está ativado e uma coluna balanceável termina curta | Linhas extras da grade por bloco e os parágrafos a quebrar com uma linha a mais (`balanceExtraPx`, `balanceLooseness`) | `MAX_BALANCING_PASSES`: 10 tentativas por segmento. `MAX_BALANCING_PASSES_PER_DOCUMENT`: 30 passadas por documento |
| **7. Faixas de fechamento** | O balanceamento está ativado com `headings.balancing.trailing`, e um capítulo ou o documento termina em uma faixa cujas colunas estão desniveladas | Um limite de fechamento para essa faixa, com as dicas de balanceamento congeladas | `MAX_BAND_PASSES`: 4 passadas de propostas e novas tentativas, cada limite com as suas 3 novas tentativas |
| **8. Polimento** | A rodada 7 manteve um limite de fechamento | O balanceamento de colunas mais uma vez, sobre a diagramação com o limite, com segmentos novos | 10 tentativas por segmento e 30 passadas, contadas do zero |

As rodadas 3 a 8 rodam uma depois da outra dentro de uma compilação (`buildDocumentBalanced`). As rodadas 1 e 2 ficam fora dela: uma rodada de sumário ou de números de nota é uma compilação inteira, com as suas próprias rodadas 3 a 8. Não há limite para a compilação como um todo, só esses.

**Toda repetição é um posicionamento completo.** Uma dica não remenda a árvore da passada anterior. A passada posiciona o documento de novo com a dica em vigor, de modo que um título com duas linhas a mais acima dele, ou uma faixa cortada seis linhas mais acima, passa pelas mesmas regras de quebra e de manter junto que todo o resto. O condutor então confere o resultado: um limite de faixa cujo boxe não caiu no corte cresce ou é descartado, e uma passada de balanceamento que não melhorou um segmento é descartada para esse segmento. O que o condutor devolve é feito de páginas que uma passada posicionou: uma passada inteira ou, depois do balanceamento de colunas, as páginas de cada segmento tiradas da melhor passada que esse segmento teve.

**O que o documento informa.** `doc.iterationCount` conta as passadas de posicionamento por trás do layout devolvido: a primeira e as das rodadas de limites de faixa, de balanceamento e de faixas de fechamento. As passadas de legendas laterais e de página da citação não entram na conta, nem as compilações anteriores de uma rodada de sumário ou de números de nota; `BuildDocumentOptions.onPass` é chamado para cada passada, com o tempo que ela levou. `doc.converged` diz respeito só ao balanceamento de colunas. É `true` quando todo segmento terminou sem nenhuma linha vazia nas suas colunas balanceáveis ou sem nenhuma alavanca a propor, e `false` quando um segmento parou em uma passada que não o melhorou, ou esgotou as tentativas, com um vão ainda aberto. Com o balanceamento desativado, é `true`. Um documento com `converged: false` está completo: contém a melhor diagramação que o condutor encontrou, e alguma coluna dele termina curta.

### Balanceamento de colunas

- **Roda:** como as rodadas 6 e 8 do condutor (ativado por padrão; `headings.balancing.enabled: false` o desativa, e ele fica desativado por padrão no texto vertical e em uma grade de caracteres, veja [Configuração → Equilíbrio de colunas](https://postext.dev/pt/docs/configuration-page-flow.md#equilíbrio-de-colunas))
- **Entrada:** a melhor diagramação até ali e as linhas vazias da grade no pé de cada coluna balanceável (`collectColumnGaps`, em `pipeline/columnBalancing.ts`)
- **Ação:** propor alavancas que preenchem cada coluna curta no próprio lugar, rodar a passada de posicionamento com elas e ficar com o resultado de um segmento quando a pontuação de vãos dele caiu
- **Saída:** colunas que terminam na última linha da grade da página, com cada alavanca registrada no bloco em que foi aplicada (`VDTBlock.balancing`)

Colunas desbalanceadas chamam a atenção na hora. As regras de quebra as deixam o tempo todo: um título que passou adiante para ficar com o seu texto, um parágrafo que passou inteiro para evitar uma viúva, uma figura que não coube. Uma coluna que para duas linhas acima da vizinha parece inacabada, como se a diagramação tivesse desistido no meio do caminho.

Nenhum bloco muda de coluna para corrigir isso. Uma quebra de coluna está presa ao elemento que não coube: o bloco que abre a coluna seguinte era alto demais para o espaço que restava, e continua alto demais para um espaço menor. Por isso uma coluna curta só pode ser preenchida por dentro, no máximo pelo tamanho do seu vão, e nada atravessa para a coluna seguinte. O ajuste é local, que é o que um compositor faz à mão: acrescentar uma linha de espaço onde o olho a aceita. Uma coluna é balanceável quando o conteúdo passou por ela e seguiu para uma coluna posterior da mesma página ou, no caso da última coluna de uma página, quando a página continua na seguinte. A última página de um capítulo pode terminar curta; a faixa de fechamento dela é cortada nivelada por um limite de fechamento (veja a [Etapa 3](https://postext.dev/pt/docs/architecture-layout-pipeline.md#etapa-3-posicionamento-em-páginas-e-colunas)).

Para cada vão, as alavancas são propostas em ordem de prioridade editorial, a mesma ordem em que [Configuração → Equilíbrio de colunas](https://postext.dev/pt/docs/configuration-page-flow.md#equilíbrio-de-colunas) as apresenta:

1. **Um boxe que fecha a coluna** ocupa o espaço que sobra sob o seu pé como espaço acima dele, de modo que o pé cai na última linha da grade da página, alinhado com a última linha da coluna ao lado. Registrado como `trailingCallout`.
2. **Imagens com área segura** (`Resource.safeArea`), inseridas em linha na coluna ou flutuadas no alto ou no pé dela só sobre essa coluna, crescem em linhas inteiras da grade, recortadas dentro da área segura. Uma imagem mais alta não deixa buraco na página, por isso essa alavanca roda antes de qualquer espaço ser somado. Registrado como `flexFigure`.
3. **Títulos** recebem linhas inteiras da grade acima deles, uma linha por vez, em rodízio, começando pelo título mais importante (o de nível mais baixo), que assim fica com a maior parte. Um título bem no alto de uma coluna não recebe nenhuma, para que as colunas continuem começando no alto da página. Um título só recebe assim as suas primeiras linhas antes de as alavancas abaixo serem tentadas, e mais, até o seu limite, quando elas não têm mais o que dar. Registrado como `heading`.
4. **Finais de lista** recebem uma linha da grade onde uma lista termina. Registrado como `listEnd`.
5. **Sob fórmulas, boxes e figuras** vai uma linha da grade: sob uma fórmula em destaque ou um boxe depois do qual o texto continua (`afterDisplay`), e entre uma figura ou tabela que encabeça a coluna e o texto abaixo dela (`afterFloat`).
6. **Parágrafos frouxos** são a última alternativa. Os parágrafos da coluna, do mais longo para o mais curto, são quebrados cada um com uma linha a mais (o `\looseness=+1` do TeX, pela opção `looseness` do quebrador de Knuth-Plass), e a composição mais longa só é aceita enquanto toda linha fica dentro de `bodyText.maxWordSpacing`. Quando o espaçamento entre palavras sozinho não consegue ganhar a linha, o parágrafo pode também usar o menor tracking positivo que consiga, sob um limite. Registrado como `looseParagraph`.

Os limites, os interruptores e as exceções (uma grade de caracteres, as páginas de fechamento, um flutuante sobre várias colunas) são chaves de `headings.balancing`. A página de Configuração as lista e é mantida em dia com elas; esta página não as repete.

Uma proposta é verificada posicionando-a. A passada que aplica as alavancas é um posicionamento completo, e de vez em quando uma alavanca acaba mesmo levando conteúdo para o outro lado de uma quebra de coluna: um parágrafo dividido cuja cabeça já não cabe, um flutuante que perdeu o seu espaço. As páginas depois desse ponto fluem de outro modo, e vãos se abrem em outros lugares. O condutor vê isso na pontuação, descarta a passada para esse segmento e põe na lista negra as alavancas recém-aplicadas na primeira coluna que mudou, de modo que a proposta seguinte mantém as alavancas que funcionaram e deixa de fora a que causou a cascata.

**O balanceamento converge por segmento.** As páginas entre quebras explícitas (uma abertura de capítulo, um `:::pagebreak`) são diagramadas de forma independente umas das outras: nada flui através de uma quebra dessas, de modo que uma alavanca de balanceamento dentro de uma sequência de páginas nunca pode mover uma linha de outra. Por isso, o laço de balanceamento de colunas julga cada *segmento* desses por conta própria. A pontuação de vãos dele é o número de linhas vazias da grade no pé das suas colunas balanceáveis. Cada passada ainda coloca o documento inteiro, mas cada segmento mantém ou rejeita a sua parte das alavancas pela sua própria pontuação de vãos, põe na lista negra as suas próprias cascatas, estabiliza por conta própria e gasta o seu próprio orçamento de tentativas; um segmento cuja nova tentativa piorou recupera as suas páginas da melhor passada que teve, enquanto os outros seguem em frente. Assim, um livro de trinta capítulos se balanceia exatamente como os seus capítulos se balanceariam um a um (o PDF do livro inteiro e o PDF de um único capítulo são idênticos para esse capítulo), em vez de uma cascata em qualquer ponto custar uma passada a todas as páginas do livro.

Os limites são uma válvula de segurança. Algumas colunas não podem ser fechadas: uma que só contém parágrafos curtos não tem alavanca para acionar, e uma cujas alavancas empurram, cada uma, um bloco para o outro lado da quebra de coluna não tem nenhuma que possa ser mantida. O motor fica com a melhor diagramação que encontrou e deixa essa coluna curta. Como as alavancas ficam registradas nos blocos, uma prova ou um teste pode listar as colunas que nada fechou (veja [Configuração → Qual recurso atuou](https://postext.dev/pt/docs/configuration-page-flow.md#qual-recurso-atuou)).
