# Uso programático: pacotes .postext

> Abrir, diagramar, renderizar e criar arquivos .postext a partir do código, pacotes bilíngues, arquivos de versões anteriores e a API de baixo nível

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

## Em poucas palavras

Esta página é para quem escreve código. Um arquivo .postext guarda um livro inteiro num só arquivo: o texto, os ajustes, as fontes e as imagens. A página mostra como abrir um arquivo desses, diagramar as páginas e desenhá-las. Mostra como criar um arquivo novo a partir do seu próprio texto e como guardar dois idiomas de um livro no mesmo arquivo. Também explica como são lidos os arquivos feitos por versões anteriores do Postext.

## Pacotes (arquivos `.postext`)

Um **arquivo `.postext`** é um livro inteiro em um único arquivo: um arquivo zip com um manifesto `preset.json`, um arquivo Markdown por capítulo, os conteúdos dos recursos (bitmaps, SVGs, matrizes de impressão em PDF) e os arquivos das fontes que a configuração nomeia. O [Sandbox](https://postext.dev/pt/docs/sandbox-books-saving.md#exportar-e-importar) o exporta e importa, e a [skill para agentes](https://postext.dev/pt/docs/skill.md) o entrega. O pacote `postext` também consegue criá-lo e abri-lo, de modo que um livro pode passar entre essas ferramentas e o seu próprio programa sem perder nada.

```
my-book.postext
├── preset.json            manifest: name, locale, chapters, config, resources, fonts
├── chapters/01-dusk.md
├── chapters/02-night.md
├── resources/lantern.svg
└── fonts/ebgaramond-400-normal.woff2
```

O manifesto é descrito campo a campo no apêndice [Formato do pacote de predefinição](https://postext.dev/pt/docs/sandbox-embedding.md#formato-do-pacote-de-predefinição) do Sandbox. Um arquivo também pode levar `layouts.json`, as contagens de páginas do Sandbox, para que o livro já abra paginado ali, ou um por edição de um livro multilíngue (`layouts.zh-Hant.json`, lido primeiro). `openBundle` os ignora.

A API é exportada pelo próprio `postext` e pelo subcaminho `postext/bundle`, que acrescenta as funções auxiliares de baixo nível. Importe de `postext` quando também for renderizar. Assim, os adaptadores de pacote e os renderizadores compartilham uma única instância do módulo, o que importa em uma CDN como o esm.sh, onde cada ponto de entrada é um build separado.

### Abrir um pacote

`openBundle` recebe os bytes do arquivo (um `Uint8Array`, um `ArrayBuffer` ou um `Blob` / `File` de um `<input type="file">`) e devolve tudo de que o motor e os seus renderizadores precisam:

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

const bundle = await openBundle(await file.arrayBuffer(), { locale: 'es' });

bundle.chapters;   // [{ title, file, markdown }, …] na ordem do livro
bundle.config;     // PostextConfig, pronto para buildDocument
bundle.resources;  // Resource[]
bundle.files;      // Map<path, Uint8Array>: todos os arquivos do pacote
```

| Campo | O que contém |
| --- | --- |
| `manifest` | O `preset.json` validado. |
| `id`, `name`, `description` | Do manifesto. `name` e `description` são os da edição lida: `localized[…].name` e `description` do idioma servido quando ele os informa; caso contrário, os do manifesto. |
| `locale`, `locales` | O idioma em que o conteúdo foi lido, e todos os idiomas que um pacote bilíngue traz. `options.locale` escolhe um: primeiro a tag exata, depois o idioma base e, por fim, o idioma do próprio pacote. |
| `chapters` | `{ title, file, markdown }` por capítulo. Um capítulo sem título no manifesto recebe o texto do seu primeiro título `#`. |
| `config` | A paleta de cores padrão e os tipos de recurso no idioma do pacote, depois o `config` do manifesto e, por fim, as substituições do idioma. O idioma do pacote é o `locale` acima, então um pacote em um único idioma recebe os seus próprios rótulos, seja qual for o `options.locale` pedido. Um manifesto que não nomeia idioma usa o que o seu `config` define (`locale` e, depois, o idioma de hifenização) ou, na falta dele, `options.locale`. `customFonts` lista as famílias de fontes do pacote. É a mesma configuração com que o Sandbox abre o pacote. |
| `resources` | Os recursos, com as legendas do idioma escolhido. Um tamanho que falta no manifesto é lido do arquivo. |
| `fonts` | Uma entrada por variante: `{ family, weight, style, format, file, bytes }`. |
| `files` | Todos os arquivos do zip, indexados pelo caminho. |
| `thumbnail`, `canvasScope` | O caminho da imagem de capa e o modo como o pacote pede para ser visto: o `view` do manifesto, com o `localized[…].view` do idioma servido por cima. |
| `start` | Onde o livro começa, para um pacote que contém parte de um mais longo: o `start` do manifesto, ou o `localized[…].start` do idioma servido. Ausente num livro que começa pelo início. |
| `warnings` | Problemas não fatais: um arquivo de fonte sem suporte, uma matriz de impressão faltando. |

O **`fileId` de um conteúdo é o seu caminho dentro do pacote**. `resource.svg.fileId`, `resource.bitmap.fileId` e o `fileId` de cada variante de `customFonts` podem ser buscados diretamente em `bundle.files`. `openBundle` lança um erro quando os bytes não são um zip, quando não há um `preset.json` válido (na raiz ou dentro de uma única pasta de primeiro nível) ou quando falta um arquivo que o manifesto nomeia.

#### Pacotes gravados pelo postext 1.4 ou anterior

Todo manifesto que `createBundle` e o Sandbox gravam leva `configVersion: 14`: as regras de configuração para as quais o seu `config` foi escrito. Um manifesto sem isso foi gravado pelo postext 1.4 ou anterior, que resolvia dezoito coisas de outra maneira:

- **Quebras de título** (regras 3): até a 1.4, um objeto `headings` sem quebra de H1 não tinha nenhuma (veja [Sobrescritas por nível](https://postext.dev/pt/docs/configuration-headings.md#sobrescritas-por-nível)).
- **O tamanho das fórmulas** (regras 4): até a 1.4, as fórmulas saíam 1,131 vez maiores do que diz `fontSizeScale` (veja [Tamanho das fórmulas](https://postext.dev/pt/docs/configuration-lists-math.md#matemática)).
- **O espaço abaixo de um recurso inline** (regras 5): até a 1.4, o texto depois de uma figura ou tabela com `placement.position: 'here'` continuava na linha seguinte da grade, sem o espaço de flutuantes abaixo dela (veja `layout.inlineResourceGap` em [Diagramação](https://postext.dev/pt/docs/configuration-page-layout.md#diagramação)).
- **O espaço em volta de um recurso inline dentro de um boxe** (regras 6): até a 1.4, esse recurso ficava encostado no texto do boxe que o envolve (veja `layout.inlineResourceGapInBoxes` em [Diagramação](https://postext.dev/pt/docs/configuration-page-layout.md#diagramação)).
- **Marcações inline nos títulos** (regras 6): até a 1.4, um título imprimia as palavras do seu `*italic*`, `**bold**` e outras marcações no seu próprio estilo simples (veja `headings.inlineMarks` em [Títulos](https://postext.dev/pt/docs/configuration-headings.md#títulos)).
- **O tamanho de uma capitular** (regras 6): até a 1.4, o `dropCap` de um texto de design sem `fontSize` era tão alto quanto todas as caixas de linha que abrange, com o topo acima da primeira linha (veja `dropCap` em [Elementos de texto](https://postext.dev/pt/docs/configuration-headers-footers.md#elementos-de-texto)).
- **O espaço abaixo de uma linha com dois-pontos** (regras 6): até a 1.4, `keepColonWithList` considerava suficiente para a lista uma linha de espaço abaixo da linha terminada em dois-pontos, e um primeiro item de duas linhas que as regras de órfãs e viúvas mantêm inteiro passava para a coluna seguinte sem ela (veja `bodyText.colonListRoom`).
- **As linhas que o corte de um boxe deixa** (regras 6): até a 1.4, um boxe que se dividia dentro de um parágrafo ou item de lista podia deixar uma linha dele de um lado, desde que cada lado do boxe tivesse as suas `splitMinLines` linhas no total (veja `layout.boxChildSplitMinLines` em [Diagramação](https://postext.dev/pt/docs/configuration-page-layout.md#diagramação)).
- **Quebras de linha em um travessão** (regras 7): até a 1.4, o Knuth-Plass nunca terminava uma linha depois de um travessão ou meia-risca colados entre palavras (`say—that’s`), e o quebrador linha a linha do texto formatado só entre duas letras (veja `bodyText.breakAfterDashes` em [Texto do corpo](https://postext.dev/pt/docs/configuration-text.md#texto-do-corpo)).
- **Texto alinhado à esquerda** (regras 7): até a 1.4, o texto corrido em bandeira era composto linha a linha, preenchendo cada linha antes da seguinte, independentemente do que dissesse `optimalLineBreaking` (veja `bodyText.optimalRagged` em [Texto do corpo](https://postext.dev/pt/docs/configuration-text.md#texto-do-corpo)).
- **A divisão abaixo de um título** (regras 8): até a 1.4, o parágrafo abaixo de um título no pé de uma coluna mantinha ali tantas linhas quantas coubessem, por poucas que passassem para a coluna seguinte (veja `headings.keepWithNextSplit` em [Títulos](https://postext.dev/pt/docs/configuration-headings.md#títulos)).
- **O espaço abaixo de um contêiner `:::paragraphs`** (regras 8): até a 1.4, o espaço do estilo era aplicado abaixo do último parágrafo antes do ajuste à grade, o espaço acima do bloco seguinte (o `marginTop` de um título) era somado abaixo dele, e o espaçamento entre parágrafos do texto ficava de fora (veja `bodyText.paragraphContainerSpacing` em [Texto do corpo](https://postext.dev/pt/docs/configuration-text.md#texto-do-corpo)).
- **Quebras de linha no hífen de uma palavra composta** (regras 8): até a 1.4, o Knuth-Plass nunca terminava uma linha justificada depois de um hífen entre duas letras (`well-known`) em um parágrafo sem formatação inline, ao passo que o fazia em um parágrafo com formatação (veja `bodyText.breakAfterHyphens` em [Texto do corpo](https://postext.dev/pt/docs/configuration-text.md#texto-do-corpo)).
- **Poemas sem separador** (regras 9): até a 1.22, um poema `:::verse` cujas linhas não traziam `||` era composto em hemistíquios soltos, cada linha centralizada (veja `bodyText.verse.layout` em [Verso](https://postext.dev/pt/docs/configuration-text.md#verso)).
- **Um recuo de primeira linha ao lado de um recuo deslocado** (regras 9): até a 1.22, o `hangingIndent` de um estilo de parágrafo substituía o seu `firstLineIndent`, e a primeira linha começava em `indent` (veja [Estilos de parágrafo](https://postext.dev/pt/docs/configuration-styles.md#estilos-de-parágrafo)).
- **Uma barra invertida no fim de uma linha** (regras 9): até a 1.22, uma barra invertida no fim de uma linha de um parágrafo, de uma citação ou de um item de lista, e `\\` antes de um espaço, eram impressas, e as linhas se uniam com um espaço (veja `bodyText.hardLineBreaks` em [Texto do corpo](https://postext.dev/pt/docs/configuration-text.md#texto-do-corpo)).
- **Cercas de código** (regras 9): até a 1.22, uma cerca ```` ``` ```` ou `~~~` e as linhas dentro dela eram lidas como Markdown: as linhas se juntavam em parágrafos, uma linha com `#` virava título, e as cercas eram impressas (veja `codeStyle.blocks` em [Listagens de código](https://postext.dev/pt/docs/configuration-styles.md#listagens-de-código)).
- **Versos partidos** (regras 10): na 1.23, um verso de um poema composto verso a verso mais largo que a mancha era partido com o seu espaçamento natural, por pouco que sobrasse (veja `bodyText.verse.tighten` em [Verso](https://postext.dev/pt/docs/configuration-text.md#verso)).

`openBundle` e `readBundle` leem o `config` de um manifesto assim, e a configuração `localized` de cada idioma, por meio de `migrateConfig`, que grava explicitamente as quebras que a 1.4 compunha e multiplica a escala das fórmulas por 1,131 (com as margens de exibição em `em` divididas por esse fator). Um manifesto marcado de `3` a `7`, gravado por uma pré-versão da 1.5, recebe apenas as fixações das regras posteriores à sua marca. Com `3`, isso é o tamanho das fórmulas, o espaço inline, as cinco fixações das regras 6, as duas das regras 7 e as três das regras 8; com `4`, o espaço inline e as fixações das regras 6, 7 e 8; com `5`, as fixações das regras 6, 7 e 8; com `6`, as das regras 7 e 8; com `7`, só as das regras 8. A divisão abaixo de um título (`pinLegacyHeadingSplit`) é gravada como `headings.keepWithNextSplit: 'fill'` no `headings` que as camadas deixam em vigor, quando algum capítulo lido tem um título e a configuração não nomeia um valor próprio, mantém `headings.keepWithNext` ativado e não desativa `bodyText.avoidOrphans`. As quebras em compostos (`pinLegacyHyphenBreaks`) são gravadas como `bodyText.breakAfterHyphens: false` no `bodyText` em vigor, quando um capítulo lido tem um hífen entre duas letras e a configuração nem já o define nem desativa `optimalLineBreaking`. O espaço abaixo dos contêineres (`pinLegacyParagraphContainerSpacing`) é gravado como `bodyText.paragraphContainerSpacing: 'add'` no `bodyText` em vigor, quando a configuração declara um estilo de parágrafo (em `paragraphStyles` ou nas substituições do visualizador HTML), um capítulo lido abre um contêiner `:::paragraphs` em uma linha própria e a configuração ainda não o define. As quebras em travessões (`pinLegacyDashBreaks`) são gravadas como `bodyText.breakAfterDashes: false` no `bodyText` em vigor, quando um capítulo lido tem um travessão ou meia-risca colados entre palavras (antes dele uma letra, um algarismo ou uma pontuação de fechamento, e depois dele uma letra, um algarismo ou um parêntese ou aspa de abertura; aspas antes dele contam quando uma letra, um algarismo, uma pontuação de fechamento ou um espaço sem quebra vem antes delas, como em `"no"—and`, mas não em `said "—Hola`; uma marcação inline encostada no travessão ou nas aspas, como os `**` de `**riddles.**—I`, conta dos dois lados) e a configuração ainda não o define. A quebra em bandeira (`pinLegacyRaggedBreaking`) é gravada como `bodyText.optimalRagged: false` no `bodyText` em vigor, quando a configuração deixa algum texto corrido em bandeira (um `textAlign` diferente de `'justify'` no texto do corpo, em um estilo de parágrafo, no corpo de um boxe, no corpo de uma parte ou no corpo de um estilo de seção (`headingStyles[].bodyStyle`), ou nas substituições do visualizador HTML), ainda não o define e não desativa `optimalLineBreaking`. O espaço em boxes (`pinLegacyBoxResourceGap`) é gravado como `layout.inlineResourceGapInBoxes: false` no `layout` em vigor, quando um recurso está incorporado em uma linha própria dentro de um `:::callout` dos capítulos lidos e a configuração ainda não o define. O corte de boxes (`pinLegacyBoxChildCut`) é gravado como `layout.boxChildSplitMinLines: 1` no `layout` em vigor, quando um capítulo lido abre um `:::callout` em uma linha própria e a configuração ainda não o define. As marcações dos títulos (`pinLegacyHeadingMarks`) são gravadas como `headings.inlineMarks: false` no `headings` que as camadas deixam em vigor, quando um título dos capítulos lidos tem uma marcação (`*`, `_`, `^`, `~`, `:smallcaps[` ou um link no seu texto) e a configuração não nomeia um valor próprio. As capitulares (`pinLegacyDropCapSize`) têm o seu tamanho da 1.4 gravado explicitamente como `dropCap.fontSize`, onde quer que estejam: na unidade da entrelinha do elemento quando ela é um comprimento, senão na unidade do seu tamanho de fonte. O espaço da linha com dois-pontos (`pinLegacyColonListRoom`) é gravado como `bodyText.colonListRoom: 'line'` no `bodyText` em vigor, quando uma lista dos capítulos lidos vem depois de uma linha terminada em dois-pontos (com linhas em branco permitidas entre elas) e a configuração nem nomeia um espaço nem desativa `keepColonWithList`. O espaço inline (`pinLegacyInlineGap`) é gravado como `layout.inlineResourceGap: 'above'` no `layout` que as camadas deixam em vigor, quando uma linha dos capítulos lidos incorpora um recurso (`::resource{id="…"}` sozinho na sua linha, como o parser o lê: uma menção no texto corrido ou em um trecho de código não conta) e a configuração não nomeia um espaço próprio. O tamanho é fixado no `math` que as camadas deixam em vigor (o `math` próprio de um idioma substitui o compartilhado), e somente quando os capítulos lidos têm um `$`: um pacote sem fórmulas mantém o seu `config` como foi escrito. Quando nem o manifesto nem o idioma nomeiam um `math`, o que está em vigor é o `baseConfig` de `readBundle` (o do próprio leitor), e ele também é fixado, já que a 1.4 compunha as fórmulas do pacote nesse tamanho: um `fontSizeScale: 1.5` de base é lido como 1,5 × 1,1312. As quebras de título da base são tomadas como estão. Assim, um pacote antigo mantém o que essas regras diagramavam, e `bundle.config` mostra as quebras, o tamanho das fórmulas, os espaços, as marcações dos títulos, os tamanhos das capitulares, o espaço da linha com dois-pontos, o corte de boxes, as quebras em travessões, as quebras em compostos, a quebra em bandeira, a divisão abaixo de um título e o espaço abaixo de contêineres com que ele é diagramado. As correções de layout da 1.5 não têm fixação e se aplicam a ele como a qualquer livro, então uma página que elas afetam ainda pode mudar (veja [Tamanho das fórmulas](https://postext.dev/pt/docs/configuration-lists-math.md#matemática) para a lista). Um `preset.json` escrito à mão para as regras de hoje define `"configVersion": 14`; marcar com ele o manifesto de um pacote antigo é também o jeito de uma linha de lê-lo com as regras de hoje (um pacote sem versão perde então também a sua fixação das quebras de título). Um manifesto marcado `8`, escrito pelo postext 1.5 a 1.22, recebe só as quatro fixações das regras 9, como também todos os anteriores: a disposição do verso (`pinLegacyVerseLayout`) é escrita como `bodyText.verse.layout: 'bayt'` no `bodyText` em vigor, quando um capítulo lido compõe um poema `:::verse` cuja abertura não nomeia disposição e cujas linhas não trazem separador de hemistíquios, e a configuração ainda não o define; os recuos emparelhados (`pinLegacyPairedIndents`) tiram o `firstLineIndent` de todo estilo de parágrafo (em `paragraphStyles` ou nos ajustes do visualizador HTML) que também define um `hangingIndent` diferente de zero; as quebras de linha forçadas (`pinLegacyHardBreaks`) são escritas como `bodyText.hardLineBreaks: false` no `bodyText` em vigor, quando um capítulo lido termina uma linha de um parágrafo, de uma citação ou de um item de lista com uma barra invertida e o bloco continua abaixo, ou põe `\\` antes de um espaço e mais texto (fora o código em linha e as fórmulas, as fórmulas em destaque, os títulos e os poemas `:::verse`), e a configuração ainda não o define; e as cercas de código (`pinLegacyCodeBlocks`) são escritas como `codeStyle.blocks: false`, quando um capítulo lido abre uma cerca ```` ``` ```` ou `~~~` (de três ou mais caracteres, com até três espaços de recuo) e a configuração ainda não o define. Um manifesto marcado `9`, gravado pelo postext 1.23, recebe só a fixação das regras 10, como também todos os anteriores: os versos partidos (`pinLegacyVerseTightening`) são escritos como `bodyText.verse.tighten: false` no `bodyText` em vigor, quando um capítulo lido compõe um poema verso a verso (uma abertura `:::verse` que nomeia `layout=lines`, ou que não nomeia disposição sobre versos sem separador de hemistíquios enquanto a configuração não compõe esses poemas como bayts) e a configuração ainda não o define. Um manifesto marcado `10`, gravado pelo postext 1.24, recebe só a fixação das regras 11, como também todos os anteriores: o equilíbrio de uma grade de caracteres (`pinLegacyGridBalancing`) é escrito como `headings.balancing.enabled: true` no `headings` em vigor, quando a configuração mesclada define `cjk.grid.enabled` em texto horizontal e não define ela mesma `enabled`, já que a 1.24 equilibrava essas páginas por padrão. As regras 11 também impedem que um título de obra seja partido depois de um só caractere, compõem os números em círculo como caracteres chineses e o texto de design CJK com as regras do corpo (#637): um manifesto marcado `10` ou anterior recebe `cjk.titleMinChars: 1` (`pinLegacyTitleBreaks`) quando um capítulo lido tem um título (《, 〈 ou `:book[`), `cjk.circledNumbers: 'western'` (`pinLegacyCircledNumbers`) quando um deles tem um número em círculo (U+2460–U+24FF, U+2776–U+2793) e `cjk.composeDesignText: false` (`pinLegacyDesignText`) quando um capítulo lido ou a própria configuração tem texto CJK; cada um no `cjk` vigente e só se a configuração ainda não o define. Elas também cortam as tabelas em linha e compõem `:::columns` no texto corrido (#634): um manifesto marcado `10` ou anterior recebe `tableStyle.splitInline: false` (`pinLegacyInlineTableSplit`) no `tableStyle` vigente quando um capítulo lido incorpora um recurso, e `layout.flowColumns: false` (`pinLegacyFlowColumns`) no `layout` vigente quando um capítulo lido abre um delimitador `:::columns` em uma linha própria; cada um só se a configuração ainda não o define. Elas também oferecem a um flutuante o topo da coluna de uma abertura de largura de página (#639): um manifesto marcado `10` ou anterior recebe `layout.floatsUnderOpener: false` (`pinLegacyOpenerHeadFloats`) no `layout` vigente quando a configuração mesclada define um nível ou um estilo de título com `span: 'page'` e um capítulo lido tem um título, só se a configuração ainda não o define. As regras de 12 fazem uma página numa grade de caracteres conter as suas linhas (#717): um manifesto marcado `11` ou anterior cuja configuração mesclada ativa `cjk.grid.enabled` recebe `bodyText.avoidWidows: true`, e na região `japan` `bodyText.avoidOrphans: true` (`pinLegacyGridBreakRules`), no `bodyText` vigente, cada uma só se a configuração ainda não a define. Elas também mantêm dois caracteres de uma nota em duas linhas de cada lado de uma quebra de linha (#720): um manifesto assim recebe `cjk.warichu.minChars: 1` (`pinLegacyNoteBreaks`) no `cjk` vigente quando um capítulo lido tem uma nota `:warichu[` ou a configuração mesclada define `citations.notes: 'warichu'`, só se a configuração ainda não o define. E na região do Japão uma palavra ocidental recebe o espaço entre han e latim junto aos seus parênteses e sinais (#721): um manifesto marcado `11` ou anterior cuja configuração mesclada resolve para a região do Japão recebe `cjk.latinSpacingEdge: 'letters'` (`pinLegacyLatinSpacingEdge`) no `cjk` vigente quando um capítulo lido tem kana ou kanji, só se a configuração ainda não o define. E um recurso posto no texto aceita `placement.captionSide` como um flutuante de coluna (#729): um manifesto marcado `11` ou anterior cuja configuração mesclada define `layout.layoutType: 'oneAndHalf'` recebe `layout.inlineCaptionSide: false` (`pinLegacyInlineCaptionSide`) quando um capítulo lido incorpora um recurso, só se a configuração ainda não o define.

As regras de 12 também mudam o equilíbrio de uma coluna curta, e um manifesto marcado `11` ou anterior mantém aquilo com que foi composto. A ordem dos recursos (#707): ele recebe `headings.balancing.maxLinesBeforeLoose` com o valor do seu próprio `maxLinesPerHeading`, ou `4` quando não define nenhum (`pinLegacyLooseOrder`), no `headings` vigente, quando um capítulo lido tem um título e a configuração mesclada equilibra as colunas, deixa um título receber mais de duas linhas e ainda não define a chave. A linha sob uma figura em várias colunas (#708): ele recebe `headings.balancing.stretchAfterWideFloats: 'always'` (`pinLegacyWideFloatStretch`) no `headings` vigente, quando um capítulo lido incorpora ou cita um recurso ou faz um boxe flutuar, e a configuração mesclada equilibra as colunas, não desliga `stretchAfterFloats` e ainda não define a chave. As quebras dentro dos grupos de um índice (#712): ele recebe `index.groups.splitMinEntries: 1` (`pinLegacyIndexGroupSplit`) no `index` vigente, quando um capítulo lido tem uma cerca `:::index` numa linha própria e a configuração ainda não define a chave. A unidade do corte por igual (#711): ele recebe `headings.balancing.trailingStep: 'grid'` (`pinLegacyTrailingStep`) no `headings` vigente, quando um capítulo lido pode pôr texto fora da entrelinha do texto base (uma cerca `:::`, uma cerca de código, uma citação em bloco, uma lista, uma fórmula em destaque ou uma citação bibliográfica), e a configuração mesclada equilibra as colunas, mantém `trailing` ou `beforeSpan` ligado e ainda não define a chave.

As regras de 12 quebram o rótulo de uma referência nos seus espaços quando o rótulo são palavras (#702). Um manifesto marcado `11` ou anterior recebe `crossRefs.breakLabels: false` (`pinLegacyRefLabelBreaks`) no `crossRefs` vigente, quando um capítulo lido contém uma referência (`:ref{…}`, `\ref{…}`, `\eqref{…}` ou um `@id` do pandoc-crossref) e a configuração não nomeia a opção: todo rótulo continua sendo a única palavra com que foi composto.

As regras de 12 compõem uma palavra sem letras (`46.`, `1367)`) na escrita do texto ao redor (#725). Um manifesto marcado `11` ou anterior recebe `bodyText.scriptContext: false` (`pinLegacyScriptContext`) no `bodyText` em vigor, seja qual for o conteúdo dos capítulos, quando a configuração não nomeia o ajuste: essa palavra é composta sozinha, como foi diagramada. A regra atua numa fonte que desenha a pontuação conforme a escrita, e ao ler um manifesto não se sabe quais fontes o livro carrega.

As regras de 13 transformam em ajuste o idioma em que o texto é composto (#764), e as de 14 fazem do idioma do próprio documento o padrão dele (#770). Um manifesto marcado `13` ou anterior recebe `bodyText.shapingLanguage: 'host'` (`pinLegacyShapingLanguage`) no `bodyText` em vigor, seja qual for o conteúdo dos capítulos, quando a configuração não nomeia o ajuste; o que nomeia um valor fica com ele. O texto dele é medido no idioma da página que hospeda o layout, e em nenhum dentro de um worker, como foi diagramado. Um manifesto marcado `13` foi gravado pelo postext 1.29: o 1.29.1 e o 1.29.2 diagramavam no idioma do host um livro que não nomeia nenhum valor, como faz a fixação; o 1.29.0, cujo padrão era `'document'`, o diagramava no do documento com um canvas e um PDF que não seguiam a medida, e ele também é lido com `'host'`. Um manifesto marcado `14` que não nomeia nenhum valor é diagramado no idioma do documento.

As regras de 13 também desenham a caixa de um item de tarefa (`- [ ]`, `- [x]`): um quadrado que o motor desenha, com uma marca quando a tarefa está concluída, e que ocupa um espaço próprio antes do texto do item (#765). Um manifesto marcado `12` ou anterior recebe `unorderedLists.taskBox: 'character'` (`pinLegacyTaskBox`) no `unorderedLists` em vigor, quando algum capítulo contém um item de tarefa e a configuração não nomeia o ajuste. A caixa dele continua sendo o caractere `☐` ou `☑` composto na fonte da lista, e o texto do item começa depois da largura desse caractere, como foi diagramado. Essa largura vinha da fonte que tivesse o caractere, uma do sistema na maioria dos livros, e não é conhecida ao ler o manifesto.

```ts
import { CONFIG_VERSION, migrateConfig } from 'postext/bundle';

migrateConfig({ headings: { fontFamily: 'Georgia' } }, undefined, { content: 'A book with no maths.' });
// => { headings: { fontFamily: 'Georgia', levels: [{ level: 1, breakBefore: { enabled: false } }] } }
migrateConfig({ math: { fontSizeScale: 1.2 } }, 3);
// => { math: { fontSizeScale: 1.35746…, marginTop: { value: 0.7072, unit: 'em' }, marginBottom: { value: 0.7072, unit: 'em' } },
//      layout: { inlineResourceGap: 'above', inlineResourceGapInBoxes: false, boxChildSplitMinLines: 1 },
//      headings: { inlineMarks: false, keepWithNextSplit: 'fill' }, bodyText: { colonListRoom: 'line', breakAfterDashes: false, breakAfterHyphens: false, verse: { layout: 'bayt', tighten: false }, hardLineBreaks: false }, codeStyle: { blocks: false } }
migrateConfig({ layout: { layoutType: 'single' } }, 4, { content: 'Text.\n\n::resource{id="fig"}' });
// => { layout: { layoutType: 'single', inlineResourceGap: 'above' } }
migrateConfig({ layout: { layoutType: 'single' } }, 5, { content: ':::callout\nText.\n\n::resource{id="fig"}\n:::' });
// => { layout: { layoutType: 'single', inlineResourceGapInBoxes: false, boxChildSplitMinLines: 1 } }
migrateConfig({ bodyText: { textAlign: 'left' } }, 6, { content: 'I say—that is all.' });
// => { bodyText: { textAlign: 'left', breakAfterDashes: false, optimalRagged: false } }
migrateConfig({ paragraphStyles: [{ id: 'verse' }] }, 7, { content: ':::paragraphs{style="verse"}\nA line.\n:::' });
// => { paragraphStyles: [{ id: 'verse' }], bodyText: { paragraphContainerSpacing: 'add' } }
migrateConfig({ bodyText: { fontFamily: 'Georgia' } }, 7, { content: 'A well-known tale.' });
// => { bodyText: { fontFamily: 'Georgia', breakAfterHyphens: false } }
migrateConfig(config, CONFIG_VERSION); // regras de hoje: o próprio `config`
```

`content` é o Markdown que a configuração diagrama (uma string ou uma lista de capítulos). Sem ele, o tamanho das fórmulas é fixado sempre que as fórmulas estão ativadas, o espaço abaixo dos contêineres sempre que a configuração declara um estilo de parágrafo, e os dois espaços, as marcações dos títulos, o espaço da linha com dois-pontos, o corte de boxes, as quebras em travessões, a divisão abaixo de um título e as quebras em compostos sempre, já que o motor não tem como saber se o livro tem uma fórmula, um contêiner `:::paragraphs`, uma figura inline, um título com marcações, uma lista introduzida por dois-pontos, um boxe, um travessão colado, um título ou uma palavra composta. A quebra em bandeira é fixada só pela configuração, com ou sem conteúdo. Migre uma configuração armazenada uma única vez e armazene-a de novo com `CONFIG_VERSION`: a fixação das fórmulas multiplica a escala, então uma configuração migrada duas vezes cresceria duas vezes. Sem ele também se fixam a disposição do verso e as cercas de código, porque o livro pode ter um poema `:::verse` sem separador ou uma cerca, e os recuos emparelhados são fixados só pela configuração. Também se fixam os versos partidos da 1.23, porque o livro pode compor um poema verso a verso.

### Diagramar e renderizar um pacote

Quatro funções auxiliares ligam um pacote aberto ao motor e aos renderizadores:

- **`loadBundleFonts(bundle)`** registra as variantes do pacote em `document.fonts`, e os bytes delas no registro de fontes do motor para as imagens SVG (`registerFontBytes`). Aguarde-a antes de diagramar, porque o layout mede o texto com as fontes que o navegador tem. As famílias que o pacote nomeia mas não traz (Google Fonts) ainda precisam ser carregadas por você, como em qualquer outro documento.
- **`registerBundleImages(bundle)`** decodifica as imagens para o renderizador de canvas (`renderPage`, `renderToCanvas`), pôsteres de vídeo incluídos. **`bundleImageUrl(bundle)`** é o resolvedor `resourceImageUrl` para `renderToHtml`, e **`bundleVideoUrl(bundle)`** o seu resolvedor `resourceVideoUrl` para os arquivos de vídeo que um pacote traz. Os dois recolorem as figuras SVG quando `diagramStyle.singleInk` está ativado, uma única vez: recolorem a marcação e marcam as imagens para que nenhum renderizador as tinja de novo (veja [Tinta única no canvas e no HTML](https://postext.dev/pt/docs/configuration-resources.md#tinta-única-no-canvas-e-no-html)). Os dois também incorporam em cada SVG as variantes que o seu texto nomeia, a partir das fontes do próprio pacote primeiro e, depois, das variantes registradas no motor (veja [Fontes no texto dos SVG](https://postext.dev/pt/docs/configuration-resources.md#fontes-no-texto-dos-svg)); `registerBundleImages(bundle, { onWarning })` e `bundleImageUrl(bundle, { onWarning })` informam uma família sem variante.
- **`buildBundle(bundle)`** diagrama os capítulos em ordem e devolve um `VDTDocument` por capítulo. Cada capítulo continua o anterior: contadores de títulos e de recursos, a parte aberta, a paridade de página e a numeração de páginas. Um capítulo que imprime o sumário (`:::toc`) ou o índice remissivo (`:::index`) recebe a estrutura do livro inteiro. Aceita as mesmas opções que `buildDocument`, mais `config` para substituir a configuração do pacote, `cache` para compartilhar um cache de medição e `metadata` (veja abaixo).
- **`bundleResourceBytes(bundle)`** e **`bundleFontProvider(bundle, { decodeWoff2, fallback })`** são as opções `resourceBytes` e `fontProvider` do `renderToPdf` do `postext-pdf`. O provedor de fontes escolhe no pacote o peso mais próximo do estilo pedido. Para uma variante `.woff2` ele precisa de `decompressWoff2`, e para uma família que o pacote não traz ele chama `fallback` com os argumentos do renderizador, `request` incluído, e repassa o que ele devolver. Um fallback que busca só o arquivo `latin` de uma família imprime como caixas vazias uma família chinesa que o pacote não incorpora; um que responde com fatias, como `sliceFontProvider` em [Fontes chinesas, japonesas e coreanas](https://postext.dev/pt/docs/configuration-pdf-output.md#fontes-chinesas-japonesas-e-coreanas), a imprime inteira.

```ts
import { openBundle, loadBundleFonts, registerBundleImages, buildBundle, renderPage,
  bundleResourceBytes, bundleFontProvider } from 'postext';
import { renderToPdf, decompressWoff2 } from 'postext-pdf';

const bundle = await openBundle(bytes);
await loadBundleFonts(bundle);
await registerBundleImages(bundle);

const docs = buildBundle(bundle);                        // um VDTDocument por capítulo
const firstPage = renderPage(docs[0].pages[0], docs[0]); // um <canvas>

const pdf = await renderToPdf(docs, {                    // o livro inteiro
  fontProvider: bundleFontProvider(bundle, { decodeWoff2: decompressWoff2, fallback: fontsource }),
  resourceBytes: bundleResourceBytes(bundle),
});
```

Para diagramar você mesmo um único capítulo, passe `bundle.chapters[i].markdown`, `bundle.resources` e `bundle.config` para `buildDocument`, como faria com qualquer documento.

**Os metadados do livro.** Como no Sandbox, o front matter do primeiro capítulo é o do livro: `buildBundle` entrega o seu `title`, o seu `author` e o resto a todos os capítulos, de modo que os cabeços `{title}` e `{author}` se mantêm em todas as páginas e o `doc.metadata` de cada capítulo os traz. Um bloco de front matter no início de um capítulo posterior é ignorado: ele é encontrado pelas suas linhas `---` e apagado sem ser analisado, então um YAML que o parser rejeitaria não causa dano. `options.metadata` fornece os valores que o front matter não define (o front matter prevalece). A contagem de páginas do livro também chega a todos os capítulos: `{bookTotalPages}` a imprime, enquanto `{totalPages}` conta as do capítulo (veja [Contagem de páginas do livro](https://postext.dev/pt/docs/configuration-headers-footers.md#contagem-de-páginas-do-livro)).

```ts
const docs = buildBundle(bundle, { metadata: { author: 'A. Author' } });
docs[3].metadata.title;   // o `title:` do primeiro capítulo
```

**Onde o livro começa.** Um pacote pode conter parte de uma publicação mais longa: as páginas 58 a 61 de uma edição, o capítulo 4 de um livro didático. Seu `start` diz o que vem antes, nos termos da `continuation` de `buildDocument`: as páginas anteriores à primeira (`pageIndexOffset`, que decide de que lado cai a página 1 e, com ele, as margens espelhadas e os cabeçalhos de páginas pares e ímpares), a numeração de página em vigor (`pageNumbering`), os contadores de títulos (`headings`), a parte aberta (`part`) e os contadores de recursos, enunciados, notas de rodapé e linhas. `createBundle` o grava como `start` em `preset.json`, `openBundle` o devolve em `bundle.start`, e `buildBundle` compõe com ele o primeiro capítulo, como faria `buildDocument({ markdown, continuation: start }, config)`, e encadeia os seguintes a partir daí; `{bookTotalPages}` conta também as páginas anteriores ao livro. Um manifesto sem `start` é lido como antes, e um leitor que não conhece o campo o ignora. `bookPageCount` não faz parte dele: quem lê conta as páginas. Num pacote com vários idiomas, `localized[…].start` dá a uma edição um começo próprio.

```ts
const { bytes } = await createBundle({
  name: 'Field notes, chapter 4',
  markdown,
  config,
  // Página 58, par, no capítulo 4.
  start: { pageIndexOffset: 57, pageNumbering: { startAt: 58 }, headings: { h1: 3 } },
});
const bundle = await openBundle(bytes);
bundle.start;                 // { pageIndexOffset: 57, pageNumbering: { startAt: 58 }, headings: { h1: 3, h2: 0, … } }
const [doc] = buildBundle(bundle);
doc.pages[0].pageLabel;       // '58'
```

### Exemplo ao vivo: abrir um pacote

O pen carrega do repositório um livro de exemplo de dois capítulos (`lantern.postext`, com tipo próprio, uma figura SVG e uma tabela). Ele registra as fontes e as imagens do pacote, diagrama o livro com `buildBundle` e pinta todas as páginas. *Make the PDF* renderiza os mesmos documentos com o `postext-pdf`, incorporando as fontes do pacote. Escolha um arquivo `.postext` seu, exportado do Sandbox, por exemplo, para vê-lo do mesmo jeito.

> **Exemplo executável: Postext · abrir um pacote .postext** — Abra um arquivo .postext com o postext, diagrame o livro e renderize-o em canvas e PDF. ([código](https://github.com/drnachio/postext/tree/main/docs/examples/open-bundle))

### Criar um pacote

`createBundle` grava um arquivo `.postext` a partir de um documento: os seus capítulos, a configuração, os recursos e os conteúdos a que eles fazem referência.

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

const { bytes, manifest, warnings } = await createBundle({
  name: 'The Lantern',
  locale: 'en',
  chapters: [
    { markdown: '# Dusk\n\nIt is drawn in :ref{id="lantern"}.' },
    { title: 'Night', markdown: '# Night\n\n…' },
  ],
  config,
  resources: [{
    id: 'lantern', typeId: 'figure', kind: 'svg', caption: 'The lantern.',
    svg: { fileId: 'lantern.svg', width: 240, height: 150 },
    createdAt: 0, updatedAt: 0,
  }],
  files: { 'lantern.svg': svgMarkup, 'garamond-regular': fontBytes },
});
```

| Entrada | Significado |
| --- | --- |
| `name`, `id`, `description`, `locale` | Os metadados do manifesto. `id` assume por padrão um slug de `name`. |
| `chapters` ou `markdown` | O livro, um `{ title?, markdown }` por capítulo, ou um documento único. |
| `config` | O `PostextConfig`. Os valores iguais aos padrões ficam fora do manifesto. |
| `resources` | Os recursos. Uma imagem nomeia o seu conteúdo por `bitmap.fileId` / `svg.fileId` (e `svg.pdfFileId` para uma matriz de impressão). |
| `files` | Os conteúdos por `fileId` (um objeto ou um `Map`): as imagens a que os recursos fazem referência e os arquivos de fonte a que as variantes de `config.customFonts` fazem referência. Os valores podem ser um `Uint8Array`, um `ArrayBuffer`, um `Blob` ou uma string (marcação SVG). |
| `thumbnail` | `{ data, mime }`: uma imagem de capa (PNG, JPEG, WebP, GIF ou SVG). |
| `canvasScope` | `'book'` pede aos visualizadores que diagramem o livro inteiro como um único canvas. |
| `start` | Onde o livro começa, para um pacote que contém parte de um mais longo: a `continuation` com que um documento avulso seria composto. É gravado como `start` do manifesto. |
| `mtime` | A data de modificação gravada em todos os arquivos do zip (um `Date`, um timestamp ou uma string de data). Se omitida, é o momento da chamada, então duas chamadas com a mesma entrada geram bytes diferentes. Passe uma data fixa e a mesma entrada gera os mesmos bytes, que você pode comparar ou calcular o hash. Um zip guarda data e hora sem fuso horário, em passos de dois segundos, de 1980 a 2099, e a data é gravada no horário local da máquina. Para bytes que coincidam em qualquer máquina, construa a data a partir de campos locais, como `new Date(1980, 0, 1)`: um timestamp ou uma string terminada em `Z` designa um instante, que cai em um horário local diferente em cada fuso (`'1980-01-01T00:00:00Z'` ainda é 1979 a oeste de UTC). Uma data fora desses anos, em horário local, lança um erro. |
| `localized` | Mais idiomas do mesmo livro, por tag de idioma: `{ es: { name?, description?, chapters?, config?, resources? } }`. As entradas acima passam então a ser o conteúdo de `locale`, que se torna obrigatório. Veja [Pacotes bilíngues](https://postext.dev/pt/docs/configuration-bundles.md#pacotes-bilíngues). |

Ela devolve os `bytes` do zip, o `manifest` gravado como `preset.json`, todos os arquivos como `files` (caminho → bytes) e uma lista de `warnings`. Os arquivos recebem o nome do id do seu recurso (`resources/lantern.svg`) ou do nome do arquivo de fonte (`fonts/…`), e os capítulos, o da sua ordem e do seu título (`chapters/01-dusk.md`). As fontes são declaradas no `fonts` do manifesto, nunca dentro de `config.customFonts`. Algumas coisas ficam de fora, cada uma com um aviso:
- um recurso ou uma variante de fonte cujo conteúdo não está em `files`
- uma variante `.woff` (o renderizador de PDF não consegue incorporá-la)
- uma família marcada com `redistributable: false`

No navegador, entregue `bytes` a um link de download: `URL.createObjectURL(new Blob([bytes], { type: 'application/zip' }))`. No Node, grave-os com `fs.writeFile`. `createBundle` e `openBundle` não precisam de DOM. Os dists usam caminhos de módulo sem extensão, então no Node puro, sem bundler, eles precisam de um hook de resolução. O arquivo `docs/examples/open-bundle/build-sample.mjs` do repositório mostra um em poucas linhas.

### Pacotes bilíngues

Um arquivo `.postext` pode levar um livro em vários idiomas, e `openBundle(bytes, { locale })` o lê em qualquer um deles. `createBundle` grava um a partir de `localized`: uma entrada por idioma extra, cada uma com o que difere do conteúdo principal (a entrada `locale`):

```ts
const { bytes, manifest } = await createBundle({
  name: 'The Lantern',
  locale: 'en',
  chapters: [{ markdown: '# Dusk\n\n…' }, { markdown: '# Night\n\n…' }],
  config,
  resources: [lanternFigure, hoursTable],
  files: { 'lantern.svg': svgEn, 'lantern-es.svg': svgEs },
  localized: {
    es: {
      name: 'El farol',
      chapters: [{ markdown: '# Anochecer\n\n…' }, { markdown: '# Noche\n\n…' }],
      config: { headings: { levels: [{ level: 1, numberingTemplate: 'Capítulo {1}' }] } },
      resources: [
        { id: 'lantern', caption: 'El farol.', svg: { fileId: 'lantern-es.svg', width: 240, height: 150 } },
        { id: 'hours', caption: 'Horas de luz.' },
      ],
    },
  },
});

const es = await openBundle(bytes, { locale: 'es' });   // capítulos, configuração e legendas em espanhol
```

- **`chapters`**: o livro naquele idioma. Os arquivos de capítulo vão para uma pasta por idioma (`chapters/en/01-dusk.md`, `chapters/es/01-anochecer.md`) e o `chapters` do manifesto vira um mapa idioma → capítulos. Um idioma sem `chapters` lê os principais; quando nenhum idioma tem capítulos próprios, eles continuam sendo uma única lista.
- **`config`**: a configuração para aquele idioma. Cada chave de primeiro nível substitui por inteiro a chave compartilhada quando o pacote é lido naquele idioma, então o `headings` acima substitui o objeto `headings` inteiro. As chaves omitidas, ou iguais às compartilhadas, são compartilhadas e não são gravadas, então passar a configuração completa do idioma funciona tão bem quanto passar só as poucas chaves que mudam. Uma chave definida com os seus padrões enquanto a compartilhada não está (`layout: {}`) é gravada como foi dada, de modo que redefine o valor compartilhado. As fontes são compartilhadas: as famílias do `customFonts` de um idioma se juntam ao `fonts` do pacote.
- **`resources`**: o texto dos recursos compartilhados, associados pelo `id`: `caption`, `note`, `altText` e a `table` de uma tabela. Uma imagem com palavras pode ter a sua própria arte: `bitmap.fileId` ou `svg.fileId` (e `svg.pdfFileId`) nomeiam outro conteúdo em `files`, gravado como `resources/es/lantern.svg`. Os outros campos, como o tipo ou o posicionamento, são compartilhados. Um id que não está entre os `resources` fica de fora com um aviso, e uma imagem de idioma que falta deixa o idioma com a compartilhada, também com um aviso.
- **`name`**, **`description`**: o título e a descrição do livro nesse idioma, gravados como `localized[…].name` e `description` quando diferem dos compartilhados. `openBundle` os devolve como `bundle.name` e `bundle.description` nesse idioma, o Sandbox lista a edição com esse título (na importação e na sua biblioteca) e `postext info` e `postext check` os mostram. Uma entrada do `index.json` de uma fonte de presets pode copiá-los em `localized` (`localized.ca.name`) para que a biblioteca mostre cada edição com o seu título antes de abri-la. Um idioma sem eles mantém o `name` e a `description` do manifesto.

O manifesto lista todos os idiomas em `locales` (`['en', 'es']`), mantém o principal como `locale` e guarda o resto em `localized`. `openBundle` sem idioma lê o idioma principal.

**Que idioma o leitor recebe.** `openBundle(bytes, { locale })` serve o idioma exato; na falta dele, o idioma base (`es-MX` lê `es`); e, na falta deste, o principal. `bundle.locale` diz qual foi servido. Os capítulos e o texto vêm sempre do mesmo idioma. O idioma principal mantém o texto compartilhado mesmo quando `localized` traz uma variante regional dele: um pacote `pt-PT` com uma entrada `pt-BR` lê as legendas brasileiras só para `pt-BR`, e as compartilhadas para `pt-PT` e `pt`.

### Exemplo ao vivo: criar um pacote

O pen constrói um livro de dois capítulos com uma figura SVG e lista os arquivos que `createBundle` gravou, junto com o manifesto. Ele oferece o zip para download e depois o abre de novo com `openBundle` e pinta a primeira página: o percurso completo em poucas linhas. Importe o arquivo baixado no Sandbox para continuar trabalhando nele lá.

> **Exemplo executável: Postext · criar um pacote .postext** — Grave um arquivo .postext com o createBundle do postext, baixe-o e abra-o de novo. ([código](https://github.com/drnachio/postext/tree/main/docs/examples/create-bundle))

### Trabalhar com pacotes

Como o Sandbox, a skill para agentes e o pacote `postext` leem e gravam o mesmo arquivo, um arquivo `.postext` é uma forma prática de passar um livro de uma ferramenta para outra:

- **Comece por um pacote.** Porte uma publicação existente com a [skill para agentes](https://postext.dev/pt/docs/skill.md), ou faça o design de um livro no [Sandbox](https://postext.dev/pt/sandbox.md) e exporte-o (**Baixar (.postext)** no menu ⋯ da linha do livro no painel Livros). Carregue o arquivo no seu programa com `openBundle` para renderizá-lo em canvas, HTML ou PDF. Mantenha o arquivo como fonte do livro: edite os capítulos, a configuração ou os recursos no código e grave-o de volta com `createBundle`, ou simplesmente recarregue-o sempre que ele mudar.
- **Depure e faça o ajuste fino no Sandbox.** Quando algo na saída do seu programa precisa de ajuste (uma figura que cai na página errada, um estilo de título, o equilíbrio das colunas), exporte com `createBundle` o que o seu programa diagrama. Importe esse arquivo no Sandbox (*Livros → Novo → Abrir um arquivo .postext…*), corrija o texto, o design ou as figuras com a visualização ao vivo, o painel Verificações e a vista PDF, e exporte-o de novo. O seu programa então carrega o arquivo corrigido com `openBundle`. Ou copie o que mudou de volta para o seu código: o `config` do manifesto contém só os valores que diferem dos padrões, então ele se lê como um diff curto.

### API de baixo nível

`postext/bundle` também exporta as peças sobre as quais `openBundle` e `createBundle` são construídas, para aplicações que guardam ou servem pacotes do seu próprio jeito (um diretório descompactado servido por HTTP, registros em um banco de dados):

- **`openBundleZip(bytes)` / `zipBundle(files, { mtime })`**: a camada do arquivo compactado. Na abertura, tolera uma pasta raiz e ignora as entradas `__MACOSX` e os arquivos ocultos. Caminhos que saem do pacote são recusados. `mtime` data os arquivos como o campo de mesmo nome na entrada de `createBundle`.
- **`readBundle(manifest, readFile, options)`** lê um manifesto e uma função `readFile(path)` e devolve capítulos, configuração, recursos, imagens e fontes. `options` define o idioma, como os identificadores de arquivo são nomeados (`ids`), a configuração base (`baseConfig`, por baixo da do manifesto; por padrão, a paleta e os tipos de recurso de `bundleBaseConfig` no idioma do pacote, que `resolveBundleConfigLocale(manifest, locale)` devolve, e uma aplicação que passe a sua própria `baseConfig` deve localizá-la para esse idioma; com um manifesto anterior a `configVersion: 4`, o seu `math` é fixado com o do pacote; com um anterior a 5, o espaço em linha do seu `layout`; com um anterior a 6, o espaço nos boxes do seu `layout`, a folga depois de dois-pontos do seu `bodyText`, as marcas em linha dos seus `headings` e o tamanho das suas capitulares; com um anterior a 7, as quebras depois de travessão e a composição em bandeira do seu `bodyText`; e com um anterior a 8, a divisão logo abaixo de um título dos seus `headings` e as quebras depois do hífen de palavras compostas e o espaço sob os contêineres `:::paragraphs` do seu `bodyText`; veja [Pacotes gravados pelo postext 1.4 ou anterior](https://postext.dev/pt/docs/configuration-bundles.md#pacotes-gravados-pelo-postext-14-ou-anterior)) e como os tamanhos intrínsecos são medidos. `readResolution` lê do arquivo a resolução de cada bitmap e a guarda em `bitmap.fileResolution`; por padrão vale true quando o pacote define `layout.bitmapResolution: 'file'`.
- **`planBundle(meta, content)` / `resolveBundleFiles(plan, sources)`**: o lado da gravação, dividido em um plano puro (nomes de arquivo e manifesto) e na resolução dos bytes por meio das funções `readBlob` / `readFont`.
- **`isBundleManifest(value)`**, as funções que escolhem o idioma (`pickChapterSpecs`, `pickLocaleOverrides`, `pickBundleView`, `resolveBundleLocale`, `resolveBundleConfigLocale`), `svgSize` / `bitmapSize` / `bitmapInfo` (os pixels de um bitmap e a resolução que o arquivo informa) e os tipos do formato (`BundleManifest`, `BundleResourceSpec`, `BundleFontFamilySpec`, …).
- **`CONFIG_VERSION`, `migrateConfig(config, configVersion, { content })`, `pinLegacyHeadingBreaks(config)`, `pinLegacyMathSize(config)`, `pinLegacyInlineGap(config)`, `pinLegacyBoxResourceGap(config)`, `pinLegacyHeadingMarks(config)`, `pinLegacyDropCapSize(config)`, `pinLegacyColonListRoom(config)`, `pinLegacyBoxChildCut(config)`, `pinLegacyDashBreaks(config)`, `pinLegacyRaggedBreaking(config)`, `pinLegacyHeadingSplit(config)`, `pinLegacyParagraphContainerSpacing(config)`, `pinLegacyHyphenBreaks(config)` e `LEGACY_MATH_SIZE`** (0,5 ÷ 0,442): uma configuração guardada, expressa nos termos atuais (veja [Pacotes gravados pelo postext 1.4 ou anterior](https://postext.dev/pt/docs/configuration-bundles.md#pacotes-gravados-pelo-postext-14-ou-anterior)). `readBundle` aplica isso; uma aplicação que guarda configurações do seu próprio jeito também pode aplicar, uma vez por cópia guardada.

O Sandbox é construído sobre essas peças. Ele acrescenta os seus próprios identificadores de armazenamento e os registros de páginas de `layouts.json`.
