# Programmatische Nutzung: .postext-Pakete

> .postext-Dateien aus Code öffnen, setzen, rendern und erstellen, zweisprachige Pakete, Dateien älterer Versionen und die API der unteren Ebene

- HTML-Fassung: https://postext.dev/de/docs/configuration-bundles
- Zuletzt aktualisiert: 2026-10-16
- Lesezeit: 27 min
- Andere Sprachen: [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), [fr](https://postext.dev/fr/docs/configuration-bundles.md), [it](https://postext.dev/it/docs/configuration-bundles.md), [pt](https://postext.dev/pt/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)

## Kurz gesagt

Diese Seite richtet sich an Menschen, die Code schreiben. Eine .postext-Datei enthält ein ganzes Buch in einer einzigen Datei: Text, Einstellungen, Schriften und Bilder. Die Seite zeigt, wie Sie eine solche Datei öffnen, ihre Seiten setzen und zeichnen. Sie zeigt, wie Sie aus Ihrem eigenen Text eine neue Datei erstellen und wie Sie zwei Sprachfassungen eines Buchs in einer Datei halten. Außerdem erklärt sie, wie Dateien gelesen werden, die ältere Versionen von Postext erzeugt haben.

## Pakete (Dateien im Format `.postext`)

Eine **`.postext`-Datei** ist ein ganzes Buch in einer Datei: ein Zip-Archiv mit einem Manifest `preset.json`, einer Markdown-Datei pro Kapitel, den Nutzdaten der Ressourcen (Bitmaps, SVGs, PDF-Druckoriginale) und den Schriftdateien, die die Konfiguration nennt. Die [Sandbox](https://postext.dev/de/docs/sandbox-books-saving.md#export-und-import) exportiert und importiert sie, und der [Agenten-Skill](https://postext.dev/de/docs/skill.md) liefert sie. Auch das Paket `postext` kann sie erstellen und öffnen, sodass ein Buch zwischen diesen Werkzeugen und Ihrem eigenen Programm wechseln kann, ohne dass etwas verloren geht.

```
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
```

Das Manifest ist Feld für Feld im Anhang [Format der Vorlagenpakete](https://postext.dev/de/docs/sandbox-embedding.md#format-der-vorlagenpakete) der Sandbox beschrieben. Eine Datei kann auch `layouts.json` enthalten, die Seitenzahlen der Sandbox, sodass sich das Buch dort bereits paginiert öffnet, oder eine pro Ausgabe eines mehrsprachigen Buches (`layouts.zh-Hant.json`, wird zuerst gelesen). `openBundle` ignoriert sie.

Die API wird aus `postext` selbst und aus dem Unterpfad `postext/bundle` exportiert, der die Hilfsfunktionen der unteren Ebene hinzufügt. Importieren Sie aus `postext`, wenn Sie auch rendern. So teilen sich die Paket-Adapter und die Renderer eine Modulinstanz, was auf einem CDN wie esm.sh wichtig ist, wo jeder Einstiegspunkt ein eigener Build ist.

### Ein Paket öffnen

`openBundle` nimmt die Bytes der Datei (ein `Uint8Array`, einen `ArrayBuffer` oder einen `Blob` / eine `File` aus einem `<input type="file">`) und gibt alles zurück, was die Engine und ihre Backends brauchen:

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

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

bundle.chapters;   // [{ title, file, markdown }, …] in book order
bundle.config;     // PostextConfig, ready for buildDocument
bundle.resources;  // Resource[]
bundle.files;      // Map<path, Uint8Array>: every file of the bundle
```

| Feld | Was es enthält |
| --- | --- |
| `manifest` | Die validierte `preset.json`. |
| `id`, `name`, `description` | Aus dem Manifest. `name` und `description` gehören zur gelesenen Ausgabe: `localized[…].name` und `description` der gelieferten Sprache, wenn sie sie angibt, sonst die des Manifests. |
| `locale`, `locales` | Die Sprache, in der der Inhalt gelesen wurde, und jede Sprache, die ein zweisprachiges Paket enthält. `options.locale` wählt eine aus: zuerst das genaue Tag, dann die Grundsprache, dann die eigene Sprache des Pakets. |
| `chapters` | `{ title, file, markdown }` pro Kapitel. Ein Kapitel ohne Titel im Manifest übernimmt den Text seiner ersten Überschrift `#`. |
| `config` | Die Standard-Farbpalette und die Ressourcentypen in der Sprache des Pakets, dann die `config` des Manifests, dann die Abweichungen der Sprache. Die Sprache des Pakets ist das obige `locale`, sodass ein Paket in einer Sprache seine eigenen Bezeichnungen erhält, gleich was `options.locale` verlangt. Ein Manifest, das keine Sprache nennt, übernimmt die, die seine `config` festlegt (`locale`, dann die Sprache der Silbentrennung), sonst `options.locale`. `customFonts` listet die Schriftfamilien des Pakets auf. Es ist dieselbe Konfiguration, mit der die Sandbox das Paket öffnet. |
| `resources` | Die Ressourcen, mit den Legenden der gewählten Sprache. Eine Größe, die im Manifest fehlt, wird aus der Datei gelesen. |
| `fonts` | Ein Eintrag pro Schnitt: `{ family, weight, style, format, file, bytes }`. |
| `files` | Jede Datei im Archiv, mit ihrem Pfad als Schlüssel. |
| `thumbnail`, `canvasScope` | Der Pfad des Umschlagbildes und wie das Paket angezeigt werden möchte: der `view` des Manifests, darüber der `localized[…].view` der ausgelieferten Sprache. |
| `start` | Wo das Buch beginnt, für ein Paket, das einen Teil eines längeren enthält: das `start` des Manifests oder das `localized[…].start` der ausgelieferten Sprache. Fehlt bei einem Buch, das an seinem Anfang beginnt. |
| `warnings` | Nicht fatale Probleme: eine nicht unterstützte Schriftdatei, ein fehlendes Druckoriginal. |

Die **`fileId` von Nutzdaten ist ihr Pfad innerhalb des Pakets**. `resource.svg.fileId`, `resource.bitmap.fileId` und die `fileId` jeder Variante von `customFonts` lassen sich direkt in `bundle.files` nachschlagen. `openBundle` wirft einen Fehler, wenn die Bytes kein Zip sind, wenn es keine gültige `preset.json` gibt (in der Wurzel oder unter einem Ordner der obersten Ebene) oder wenn eine Datei fehlt, die das Manifest nennt.

#### Pakete, die postext 1.4 oder früher geschrieben hat

Jedes Manifest, das `createBundle` und die Sandbox schreiben, trägt `configVersion: 14`: die Konfigurationsregeln, für die seine `config` geschrieben wurde. Ein Manifest ohne diese Angabe stammt von postext 1.4 oder früher, das achtzehn Dinge anders handhabte:

- **Umbrüche vor Überschriften** (Regeln 3): Bis 1.4 hatte ein Objekt `headings` ohne Umbruch vor H1 keinen (siehe [Überschreibungen pro Ebene](https://postext.dev/de/docs/configuration-headings.md#überschreibungen-pro-ebene)).
- **Die Größe von Formeln** (Regeln 4): Bis 1.4 fielen Formeln 1,131-mal größer aus, als `fontSizeScale` angibt (siehe [Formelgröße](https://postext.dev/de/docs/configuration-lists-math.md#mathematik)).
- **Der Raum unter einer Inline-Ressource** (Regeln 5): Bis 1.4 setzte der Text nach einer Abbildung oder Tabelle mit `placement.position: 'here'` an der nächsten Rasterlinie fort, ohne Abstand für Gleitobjekte darunter (siehe `layout.inlineResourceGap` unter [Layout](https://postext.dev/de/docs/configuration-page-layout.md#layout)).
- **Der Raum um eine Inline-Ressource in einem Kasten** (Regeln 6): Bis 1.4 saß eine solche Ressource direkt am Text des Kastens um sie herum (siehe `layout.inlineResourceGapInBoxes` unter [Layout](https://postext.dev/de/docs/configuration-page-layout.md#layout)).
- **Inline-Auszeichnungen in Überschriften** (Regeln 6): Bis 1.4 druckte eine Überschrift die Wörter ihrer Auszeichnungen `*italic*`, `**bold**` und anderer in ihrem eigenen einfachen Stil (siehe `headings.inlineMarks` unter [Überschriften](https://postext.dev/de/docs/configuration-headings.md#überschriften)).
- **Die Größe einer Initiale** (Regeln 6): Bis 1.4 war das `dropCap` eines Gestaltungstextes ohne `fontSize` so hoch wie alle Zeilenkästen, die es überspannt, mit seiner Oberkante über der ersten Zeile (siehe `dropCap` unter [Textelemente](https://postext.dev/de/docs/configuration-headers-footers.md#textelemente)).
- **Der Raum unter einer Doppelpunktzeile** (Regeln 6): Bis 1.4 hielt `keepColonWithList` eine Zeile Raum unter der Zeile, die mit einem Doppelpunkt endet, für die Liste für ausreichend, und ein zweizeiliger erster Listenpunkt, den die Regeln für Waisen und Witwen zusammenhalten, ging ohne sie in die nächste Spalte (siehe `bodyText.colonListRoom`).
- **Die Zeilen, die ein Kastenschnitt übrig lässt** (Regeln 6): Bis 1.4 konnte ein Kasten, der innerhalb eines Absatzes oder Listenpunkts geteilt wurde, eine Zeile davon auf einer Seite lassen, solange jede Seite des Kastens insgesamt ihre `splitMinLines` Zeilen enthielt (siehe `layout.boxChildSplitMinLines` unter [Layout](https://postext.dev/de/docs/configuration-page-layout.md#layout)).
- **Zeilenumbrüche an einem Strich** (Regeln 7): Bis 1.4 beendete Knuth-Plass nie eine Zeile nach einem Geviert- oder Halbgeviertstrich, der ohne Abstand zwischen Wörtern stand (`say—that’s`), und der zeilenweise Umbrecher für formatierten Text nur zwischen zwei Buchstaben (siehe `bodyText.breakAfterDashes` unter [Fließtext](https://postext.dev/de/docs/configuration-text.md#fließtext)).
- **Flattersatz** (Regeln 7): Bis 1.4 wurde fließender Text im Flattersatz Zeile für Zeile gesetzt, jede Zeile vor der nächsten gefüllt, gleich was `optimalLineBreaking` besagte (siehe `bodyText.optimalRagged` unter [Fließtext](https://postext.dev/de/docs/configuration-text.md#fließtext)).
- **Die Teilung unter einer Überschrift** (Regeln 8): Bis 1.4 behielt der Absatz unter einer Überschrift am Fuß einer Spalte so viele Zeilen, wie dort Platz hatten, wie wenige auch in die nächste Spalte gingen (siehe `headings.keepWithNextSplit` unter [Überschriften](https://postext.dev/de/docs/configuration-headings.md#überschriften)).
- **Der Raum unter einem Container `:::paragraphs`** (Regeln 8): Bis 1.4 wurde der Abstand des Formats vor dem Einrasten ins Raster unter den letzten Absatz gesetzt, der Abstand über dem nächsten Block (das `marginTop` einer Überschrift) wurde darunter addiert, und der Absatzabstand des Textes entfiel (siehe `bodyText.paragraphContainerSpacing` unter [Fließtext](https://postext.dev/de/docs/configuration-text.md#fließtext)).
- **Zeilenumbrüche am Bindestrich eines Kompositums** (Regeln 8): Bis 1.4 beendete Knuth-Plass in einem Absatz ohne Inline-Formatierung nie eine Zeile im Blocksatz nach einem Bindestrich zwischen zwei Buchstaben (`well-known`), in einem Absatz mit Formatierung dagegen schon (siehe `bodyText.breakAfterHyphens` unter [Fließtext](https://postext.dev/de/docs/configuration-text.md#fließtext)).
- **Gedichte ohne Trenner** (Regeln 9): Bis 1.22 wurde ein Gedicht `:::verse`, dessen Zeilen kein `||` trugen, als einzelne Halbverse gesetzt, jede Zeile zentriert (siehe `bodyText.verse.layout` unter [Vers](https://postext.dev/de/docs/configuration-text.md#vers)).
- **Ein Erstzeileneinzug neben einem hängenden Einzug** (Regeln 9): Bis 1.22 ersetzte das `hangingIndent` eines Absatzformats dessen `firstLineIndent`, und die erste Zeile begann bei `indent` (siehe [Absatzformate](https://postext.dev/de/docs/configuration-styles.md#absatzformate)).
- **Ein Backslash am Zeilenende** (Regeln 9): Bis 1.22 wurde ein Backslash, der eine Zeile eines Absatzes, eines Zitats oder eines Listenpunkts beendete, sowie `\\` vor einem Leerzeichen gedruckt, und die Zeilen wurden mit einem Leerzeichen verbunden (siehe `bodyText.hardLineBreaks` unter [Fließtext](https://postext.dev/de/docs/configuration-text.md#fließtext)).
- **Codezäune** (Regeln 9): Bis 1.22 wurden ein Codeblock ```` ``` ```` oder `~~~` und die Zeilen darin als Markdown gelesen: Zeilen wurden zu Absätzen verbunden, eine Zeile mit `#` wurde zur Überschrift, die Codezäune wurden gedruckt (siehe `codeStyle.blocks` unter [Code-Listings](https://postext.dev/de/docs/configuration-styles.md#code-listings)).
- **Umbrüche in Versen** (Regeln 10): In 1.23 wurde eine Zeile eines zeilenweise gesetzten Gedichts, die breiter als die Satzbreite war, mit ihren natürlichen Wortabständen umbrochen, wie wenig sie auch überstand (siehe `bodyText.verse.tighten` unter [Vers](https://postext.dev/de/docs/configuration-text.md#vers)).

`openBundle` und `readBundle` lesen die `config` eines solchen Manifests und die Konfiguration `localized` jeder Sprache über `migrateConfig`, das die Umbrüche, die 1.4 gesetzt hat, ausschreibt und den Formelmaßstab mit 1,131 multipliziert (seine Anzeigeränder in `em` durch diesen Wert geteilt). Ein Manifest mit dem Stempel `3` bis `7`, geschrieben von einer Vorabversion von 1.5, erhält nur die Fixierungen der Regeln nach seinem Stempel. Mit `3` sind das die Formelgröße, der Inline-Abstand, die fünf Fixierungen der Regeln 6, die zwei der Regeln 7 und die drei der Regeln 8; mit `4` der Inline-Abstand und die Fixierungen der Regeln 6, 7 und 8; mit `5` die Fixierungen der Regeln 6, 7 und 8; mit `6` die Fixierungen der Regeln 7 und 8; mit `7` nur die Fixierungen der Regeln 8. Die Teilung unter einer Überschrift (`pinLegacyHeadingSplit`) wird als `headings.keepWithNextSplit: 'fill'` in die `headings` geschrieben, die die Schichten gelten lassen, wenn ein gelesenes Kapitel eine Überschrift hat und die Konfiguration keinen eigenen Wert nennt, `headings.keepWithNext` eingeschaltet lässt und `bodyText.avoidOrphans` nicht ausschaltet. Die Umbrüche an Komposita (`pinLegacyHyphenBreaks`) werden als `bodyText.breakAfterHyphens: false` in das geltende `bodyText` geschrieben, wenn ein gelesenes Kapitel einen Bindestrich zwischen zwei Buchstaben setzt und die Konfiguration den Wert weder bereits festlegt noch `optimalLineBreaking` ausschaltet. Der Raum unter Containern (`pinLegacyParagraphContainerSpacing`) wird als `bodyText.paragraphContainerSpacing: 'add'` in das geltende `bodyText` geschrieben, wenn die Konfiguration ein Absatzformat deklariert (in `paragraphStyles` oder in den Abweichungen des HTML-Betrachters), ein gelesenes Kapitel einen Container `:::paragraphs` auf einer eigenen Zeile öffnet und die Konfiguration den Wert nicht bereits festlegt. Die Umbrüche an Strichen (`pinLegacyDashBreaks`) werden als `bodyText.breakAfterDashes: false` in das geltende `bodyText` geschrieben, wenn ein gelesenes Kapitel einen Geviert- oder Halbgeviertstrich ohne Abstand zwischen Wörtern setzt (davor ein Buchstabe, eine Ziffer oder ein schließendes Satzzeichen, danach ein Buchstabe, eine Ziffer oder eine öffnende Klammer oder ein öffnendes Anführungszeichen; ein Anführungszeichen davor zählt, wenn vor dem Anführungszeichen ein Buchstabe, eine Ziffer, ein schließendes Satzzeichen oder ein geschütztes Leerzeichen steht, wie in `"no"—and`, nicht in `said "—Hola`; eine Inline-Auszeichnung, die den Strich oder das Anführungszeichen berührt, etwa das `**` von `**riddles.**—I`, zählt auf beiden Seiten) und die Konfiguration den Wert nicht bereits festlegt. Der Umbruch im Flattersatz (`pinLegacyRaggedBreaking`) wird als `bodyText.optimalRagged: false` in das geltende `bodyText` geschrieben, wenn die Konfiguration fließenden Text im Flattersatz setzt (ein anderes `textAlign` als `'justify'` im Grundtext, einem Absatzformat, dem Inhalt eines Kastens, dem Inhalt eines Teils oder dem Inhalt eines Abschnittsformats (`headingStyles[].bodyStyle`) oder in den Abweichungen des HTML-Betrachters), den Wert nicht bereits festlegt und `optimalLineBreaking` nicht ausschaltet. Der Abstand in Kästen (`pinLegacyBoxResourceGap`) wird als `layout.inlineResourceGapInBoxes: false` in das geltende `layout` geschrieben, wenn eine Ressource auf einer eigenen Zeile in einem `:::callout` der gelesenen Kapitel eingebettet ist und die Konfiguration den Wert nicht bereits festlegt. Der Kastenschnitt (`pinLegacyBoxChildCut`) wird als `layout.boxChildSplitMinLines: 1` in das geltende `layout` geschrieben, wenn ein gelesenes Kapitel ein `:::callout` auf einer eigenen Zeile öffnet und die Konfiguration den Wert nicht bereits festlegt. Die Auszeichnungen in Überschriften (`pinLegacyHeadingMarks`) werden als `headings.inlineMarks: false` in die `headings` geschrieben, die die Schichten gelten lassen, wenn eine Überschrift der gelesenen Kapitel eine Auszeichnung trägt (`*`, `_`, `^`, `~`, `:smallcaps[` oder einen Link im Titel) und die Konfiguration keinen eigenen Wert nennt. Bei den Initialen (`pinLegacyDropCapSize`) wird ihre Größe aus 1.4 als `dropCap.fontSize` ausgeschrieben, wo immer sie stehen: in der Einheit des Zeilenabstands des Elements, wenn dieser eine Länge ist, sonst in der Einheit seiner Schriftgröße. Der Raum unter der Doppelpunktzeile (`pinLegacyColonListRoom`) wird als `bodyText.colonListRoom: 'line'` in das geltende `bodyText` geschrieben, wenn eine Liste der gelesenen Kapitel auf eine Zeile folgt, die mit einem Doppelpunkt endet (Leerzeilen dazwischen erlaubt), und die Konfiguration weder einen Raum nennt noch `keepColonWithList` ausschaltet. Der Inline-Abstand (`pinLegacyInlineGap`) wird als `layout.inlineResourceGap: 'above'` in das `layout` geschrieben, das die Schichten gelten lassen, wenn eine Zeile der gelesenen Kapitel eine Ressource einbettet (`::resource{id="…"}` allein auf seiner Zeile, so wie der Parser es liest: Eine Erwähnung im Fließtext oder in einem Code-Span zählt nicht) und die Konfiguration keinen eigenen Abstand nennt. Die Größe wird in der `math` fixiert, die die Schichten gelten lassen (eine eigene `math` einer Sprache ersetzt die gemeinsame), und nur, wenn die gelesenen Kapitel ein `$` enthalten: Ein Paket ohne Formeln behält seine `config` so, wie sie geschrieben ist. Nennen weder das Manifest noch die Sprache eine `math`, ist die geltende die der `baseConfig` von `readBundle` (die des Lesers selbst), und auch sie wird fixiert, da 1.4 die Formeln des Pakets in dieser Größe setzte: Ein Basiswert `fontSizeScale: 1.5` wird als 1,5 × 1,1312 gelesen. Die Umbrüche vor Überschriften aus der Basis werden so übernommen, wie sie sind. Ein altes Paket behält also, was diese Regeln gesetzt haben, und `bundle.config` zeigt die Umbrüche, die Formelgröße, die Abstände, die Auszeichnungen in Überschriften, die Größen der Initialen, den Raum unter der Doppelpunktzeile, den Kastenschnitt, die Umbrüche an Strichen, die Umbrüche an Komposita, den Umbruch im Flattersatz, die Teilung unter einer Überschrift und den Raum unter Containern, mit denen es gesetzt wird. Die Layout-Korrekturen von 1.5 haben keine Fixierung und gelten dafür wie für jedes Buch, sodass sich eine Seite, die sie betreffen, dennoch verschieben kann (die Liste finden Sie unter [Formelgröße](https://postext.dev/de/docs/configuration-lists-math.md#mathematik)). Eine von Hand für die heutigen Regeln geschriebene `preset.json` setzt `"configVersion": 14`; das Manifest eines alten Pakets damit zu stempeln ist auch der einzeilige Weg, es mit den heutigen Regeln zu lesen (ein Paket ohne Version verliert dann auch seine Fixierung der Umbrüche vor Überschriften). Ein Manifest mit dem Stempel `8`, geschrieben von postext 1.5 bis 1.22, erhält nur die vier Fixierungen der Regeln 9, wie auch jedes ältere: Die Versanordnung (`pinLegacyVerseLayout`) wird als `bodyText.verse.layout: 'bayt'` in das geltende `bodyText` geschrieben, wenn ein gelesenes Kapitel ein Gedicht `:::verse` setzt, dessen Zaun keine Anordnung nennt und dessen Zeilen keinen Halbverstrenner tragen, und die Konfiguration den Wert nicht bereits festlegt; die gepaarten Einzüge (`pinLegacyPairedIndents`) streichen das `firstLineIndent` jedes Absatzformats (in `paragraphStyles` oder in den Abweichungen des HTML-Betrachters), das auch ein `hangingIndent` ungleich null festlegt; die erzwungenen Zeilenumbrüche (`pinLegacyHardBreaks`) werden als `bodyText.hardLineBreaks: false` in das geltende `bodyText` geschrieben, wenn ein gelesenes Kapitel eine Zeile eines Absatzes, eines Zitats oder eines Listenpunkts mit einem Backslash beendet und der Block darunter weitergeht, oder `\\` vor einem Leerzeichen und weiterem Text setzt (Inline-Code und Inline-Formeln, abgesetzte Formeln, Überschriften und Gedichte `:::verse` ausgenommen), und die Konfiguration den Wert nicht bereits festlegt; und die Codezäune (`pinLegacyCodeBlocks`) werden als `codeStyle.blocks: false` geschrieben, wenn ein gelesenes Kapitel einen Codeblock ```` ``` ```` oder `~~~` öffnet (drei oder mehr Zeichen, bis zu drei Leerzeichen eingerückt) und die Konfiguration den Wert nicht bereits festlegt. Ein Manifest mit dem Stempel `9`, geschrieben von postext 1.23, erhält nur die Fixierung der Regeln 10, wie auch jedes ältere: Die Umbrüche in Versen (`pinLegacyVerseTightening`) werden als `bodyText.verse.tighten: false` in das geltende `bodyText` geschrieben, wenn ein gelesenes Kapitel ein Gedicht zeilenweise setzt (ein Zaun `:::verse`, der `layout=lines` nennt oder keine Anordnung über Zeilen ohne Halbverstrenner nennt, während die Konfiguration solche Gedichte nicht als Bayts setzt) und die Konfiguration den Wert nicht bereits festlegt. Ein Manifest mit dem Stempel `10`, geschrieben von postext 1.24, erhält nur die Fixierung der Regeln 11, wie auch jedes ältere: Der Ausgleich eines Zeichenrasters (`pinLegacyGridBalancing`) wird als `headings.balancing.enabled: true` in die geltenden `headings` geschrieben, wenn die zusammengeführte Konfiguration `cjk.grid.enabled` bei horizontalem Text festlegt und `enabled` nicht selbst festlegt, da 1.24 eine solche Seite standardmäßig ausglich. Die Regeln 11 bewahren außerdem Buchtitel vor Umbrüchen nach einem Zeichen, setzen eingekreiste Zahlen als chinesische Zeichen und setzen CJK-Gestaltungstext nach den Regeln des Grundtextes (#637): Ein Manifest mit dem Stempel `10` oder älter erhält `cjk.titleMinChars: 1` (`pinLegacyTitleBreaks`), wenn ein gelesenes Kapitel einen Titel setzt (《 〈 oder `:book[`), `cjk.circledNumbers: 'western'` (`pinLegacyCircledNumbers`), wenn eines eine eingekreiste Zahl setzt (U+2460–U+24FF, U+2776–U+2793), und `cjk.composeDesignText: false` (`pinLegacyDesignText`), wenn ein gelesenes Kapitel oder die Konfiguration selbst CJK-Text enthält, jeweils im geltenden `cjk` und nur, wenn die Konfiguration den Wert nicht bereits festlegt. Sie teilen außerdem Inline-Tabellen und setzen `:::columns` im Fließtext (#634): Ein Manifest mit dem Stempel `10` oder älter erhält `tableStyle.splitInline: false` (`pinLegacyInlineTableSplit`) im geltenden `tableStyle`, wenn ein gelesenes Kapitel eine Ressource einbettet, und `layout.flowColumns: false` (`pinLegacyFlowColumns`) im geltenden `layout`, wenn ein gelesenes Kapitel einen Zaun `:::columns` auf einer eigenen Zeile öffnet, jeweils nur, wenn die Konfiguration den Wert nicht bereits festlegt. Sie bieten einem Gleitobjekt auch den Kopf der eigenen Spalte eines Auftakts über die ganze Seitenbreite an (#639): Ein Manifest mit dem Stempel `10` oder älter erhält `layout.floatsUnderOpener: false` (`pinLegacyOpenerHeadFloats`) im geltenden `layout`, wenn die zusammengeführte Konfiguration eine Überschriftenebene oder ein Überschriftenformat mit `span: 'page'` festlegt und ein gelesenes Kapitel eine Überschrift hat, nur wenn die Konfiguration den Wert nicht bereits festlegt. Die Regeln 12 lassen eine Seite auf einem Zeichenraster ihre Zeilen halten (#717): Ein Manifest mit dem Stempel `11` oder älter, dessen zusammengeführte Konfiguration `cjk.grid.enabled` einschaltet, erhält `bodyText.avoidWidows: true` und in der Region `japan` `bodyText.avoidOrphans: true` (`pinLegacyGridBreakRules`) im geltenden `bodyText`, jeweils nur, wenn die Konfiguration den Wert nicht bereits festlegt. Sie halten außerdem zwei Zeichen einer zweireihigen Anmerkung auf jeder Seite eines Zeilenumbruchs zusammen (#720): Ein solches Manifest erhält `cjk.warichu.minChars: 1` (`pinLegacyNoteBreaks`) im geltenden `cjk`, wenn ein gelesenes Kapitel eine Anmerkung `:warichu[` setzt oder die zusammengeführte Konfiguration `citations.notes: 'warichu'` festlegt, nur wenn die Konfiguration den Wert nicht bereits festlegt. Und in der Region Japan erhält ein westliches Wort den Abstand zwischen Han und Latein auch neben seinen Klammern und Zeichen (#721): Ein Manifest mit dem Stempel `11` oder älter, dessen zusammengeführte Konfiguration zur Region Japan aufgelöst wird, erhält `cjk.latinSpacingEdge: 'letters'` (`pinLegacyLatinSpacingEdge`) im geltenden `cjk`, wenn ein gelesenes Kapitel Kana oder Kanji enthält, nur wenn die Konfiguration den Wert nicht bereits festlegt. Und eine im Text gesetzte Ressource übernimmt `placement.captionSide` so, wie es ein Spaltengleitobjekt tut (#729): Ein Manifest mit dem Stempel `11` oder älter, dessen zusammengeführte Konfiguration `layout.layoutType: 'oneAndHalf'` festlegt, erhält `layout.inlineCaptionSide: false` (`pinLegacyInlineCaptionSide`), wenn ein gelesenes Kapitel eine Ressource einbettet, nur wenn die Konfiguration den Wert nicht bereits festlegt.

Die Regeln 12 ändern außerdem, wie eine kurze Spalte ausgeglichen wird, und ein Manifest mit dem Stempel `11` oder älter behält, womit es gesetzt wurde. Die Reihenfolge der Hebel (#707): Es erhält `headings.balancing.maxLinesBeforeLoose`, gesetzt auf sein eigenes `maxLinesPerHeading` oder auf `4`, wenn es keines festlegt (`pinLegacyLooseOrder`), in den geltenden `headings`, wenn ein gelesenes Kapitel eine Überschrift hat und die zusammengeführte Konfiguration ihre Spalten ausgleicht, einer Überschrift mehr als zwei Zeilen erlaubt und den Schlüssel nicht bereits festlegt. Die Zeile unter einer Abbildung über mehrere Spalten (#708): Es erhält `headings.balancing.stretchAfterWideFloats: 'always'` (`pinLegacyWideFloatStretch`) in den geltenden `headings`, wenn ein gelesenes Kapitel eine Ressource einbettet oder zitiert oder einen Kasten gleiten lässt und die zusammengeführte Konfiguration ihre Spalten ausgleicht, `stretchAfterFloats` nicht ausschaltet und den Schlüssel nicht bereits festlegt. Die Umbrüche innerhalb der Gruppen eines Registers (#712): Es erhält `index.groups.splitMinEntries: 1` (`pinLegacyIndexGroupSplit`) im geltenden `index`, wenn ein gelesenes Kapitel einen Zaun `:::index` auf einer eigenen Zeile setzt und die Konfiguration den Schlüssel nicht bereits festlegt. Die Einheit des Ebenenschnitts (#711): Es erhält `headings.balancing.trailingStep: 'grid'` (`pinLegacyTrailingStep`) in den geltenden `headings`, wenn ein gelesenes Kapitel Text abseits des Zeilenabstands des Grundtextes setzen kann (ein Zaun `:::`, ein Codeblock, ein Zitat, eine Liste, eine abgesetzte Formel oder ein Literaturzitat) und die zusammengeführte Konfiguration ihre Spalten ausgleicht, `trailing` oder `beforeSpan` eingeschaltet lässt und den Schlüssel nicht bereits festlegt.

Die Regeln 12 brechen die Bezeichnung eines Verweises an ihren Leerzeichen um, wenn die Bezeichnung aus Wörtern besteht (#702). Ein Manifest mit dem Stempel `11` oder älter erhält `crossRefs.breakLabels: false` (`pinLegacyRefLabelBreaks`) in den geltenden `crossRefs`, wenn ein gelesenes Kapitel einen Verweis enthält (`:ref{…}`, `\ref{…}`, `\eqref{…}` oder ein `@id` von pandoc-crossref) und die Konfiguration die Einstellung nicht nennt: Jede Bezeichnung bleibt das eine Wort, als das sie gesetzt wurde.

Die Regeln 12 formen ein Wort ohne Buchstaben (`46.`, `1367)`) in der Schrift des umgebenden Textes (#725). Ein Manifest mit dem Stempel `11` oder älter erhält `bodyText.scriptContext: false` (`pinLegacyScriptContext`) im geltenden `bodyText`, gleich was seine Kapitel enthalten, wenn die Konfiguration die Einstellung nicht nennt: Ein solches Wort wird für sich allein geformt, so wie es gesetzt wurde. Die Regel wirkt in einem Schnitt, der seine Satzzeichen je nach Schriftsystem zeichnet, und welche Schnitte ein Buch lädt, ist beim Lesen seines Manifests nicht bekannt.

Die Regeln 13 machen die Sprache, in der der Text geformt wird, zu einer Einstellung (#764), und die Regeln 14 machen die eigene Sprache des Dokuments zu ihrem Standard (#770). Ein Manifest mit dem Stempel `13` oder älter erhält `bodyText.shapingLanguage: 'host'` (`pinLegacyShapingLanguage`) im geltenden `bodyText`, gleich was seine Kapitel enthalten, wenn die Konfiguration die Einstellung nicht nennt; eines, das einen Wert nennt, behält ihn. Sein Text wird in der Sprache der Seite gemessen, die das Layout ausführt, und in einem Worker in keiner, so wie er gesetzt wurde. Ein Manifest mit dem Stempel `13` wurde von postext 1.29 geschrieben: 1.29.1 und 1.29.2 setzten ein Buch, das keinen Wert nennt, in der Sprache des Hosts, wie es die Fixierung tut; 1.29.0, dessen Standard `'document'` war, setzte es in der des Dokuments, mit einem Canvas und einem PDF, die der Messung nicht folgten, und es wird ebenfalls mit `'host'` gelesen. Ein Manifest mit dem Stempel `14`, das keinen Wert nennt, wird in der Sprache des Dokuments gesetzt.

Die Regeln 13 zeichnen außerdem den Kasten eines Aufgabenpunkts (`- [ ]`, `- [x]`): ein Quadrat, das die Engine zeichnet, mit einem Häkchen, wenn der Punkt erledigt ist, und das vor dem Text des Punkts einen eigenen Raum einnimmt (#765). Ein Manifest mit dem Stempel `12` oder älter erhält `unorderedLists.taskBox: 'character'` (`pinLegacyTaskBox`) in den geltenden `unorderedLists`, wenn ein Kapitel einen Aufgabenpunkt enthält und die Konfiguration die Einstellung nicht nennt. Sein Kasten bleibt das Zeichen `☐` oder `☑` in der Schrift der Liste, und der Text des Punkts beginnt nach der Breite dieses Zeichens, so wie er gesetzt wurde. Diese Breite stammte von der Schrift, die das Zeichen gerade enthielt, in den meisten Büchern einer Systemschrift, und ist beim Lesen des Manifests nicht bekannt.

```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); // today's rules: `config` itself
```

`content` ist das Markdown, das die Konfiguration setzt (ein String oder eine Liste von Kapiteln). Ohne diese Angabe wird die Formelgröße immer fixiert, wenn Formeln eingeschaltet sind, der Raum unter Containern immer, wenn die Konfiguration ein Absatzformat deklariert, und die beiden Abstände, die Auszeichnungen in Überschriften, der Raum unter der Doppelpunktzeile, der Kastenschnitt, die Umbrüche an Strichen, die Teilung unter einer Überschrift und die Umbrüche an Komposita immer, da die Engine nicht erkennen kann, ob das Buch eine Formel, einen Container `:::paragraphs`, eine Inline-Abbildung, eine ausgezeichnete Überschrift, eine durch einen Doppelpunkt eingeleitete Liste, einen Kasten, einen Strich ohne Abstand, eine Überschrift oder ein Kompositum enthält. Der Umbruch im Flattersatz wird allein nach der Konfiguration fixiert, mit oder ohne Inhalt. Migrieren Sie eine gespeicherte Konfiguration einmal und speichern Sie sie erneut unter `CONFIG_VERSION`: Die Fixierung der Formeln multipliziert den Maßstab, sodass eine zweimal migrierte Konfiguration zweimal wachsen würde. Die Versanordnung und die Codezäune werden ohne die Angabe ebenfalls fixiert, da das Buch ein Gedicht `:::verse` ohne Trenner oder einen Codeblock enthalten kann, und die gepaarten Einzüge werden allein nach der Konfiguration fixiert. Ebenso die Umbrüche in Versen aus 1.23, da das Buch ein Gedicht zeilenweise setzen kann.

### Ein Paket setzen und rendern

Vier Hilfsfunktionen verbinden ein geöffnetes Paket mit der Engine und den Backends:

- **`loadBundleFonts(bundle)`** registriert die Schnitte des Pakets in `document.fonts` und ihre Bytes in der Schriftregistrierung der Engine für SVG-Bilder (`registerFontBytes`). Warten Sie vor dem Setzen darauf, denn das Layout misst Text mit den Schriften, die der Browser hat. Familien, die das Paket nennt, aber nicht mitbringt (Google Fonts), müssen Sie weiterhin selbst laden, wie bei jedem anderen Dokument.
- **`registerBundleImages(bundle)`** dekodiert die Bilder für das Canvas-Backend (`renderPage`, `renderToCanvas`), Vorschaubilder von Videos eingeschlossen. **`bundleImageUrl(bundle)`** ist der Resolver `resourceImageUrl` für `renderToHtml` und **`bundleVideoUrl(bundle)`** sein Resolver `resourceVideoUrl` für die Videodateien, die ein Paket enthält. Beide färben SVG-Abbildungen um, wenn `diagramStyle.singleInk` eingeschaltet ist, und zwar einmal: Sie färben das Markup um und markieren die Bilder, damit kein Backend sie erneut tönt (siehe [Einfarbmodus auf dem Canvas und in HTML](https://postext.dev/de/docs/configuration-resources.md#einfarbmodus-auf-dem-canvas-und-in-html)). Beide betten außerdem in jedes SVG die Schnitte ein, die sein Text nennt, zuerst aus den eigenen Schriften des Pakets, dann aus den in der Engine registrierten Schnitten (siehe [Schriften in SVG-Text](https://postext.dev/de/docs/configuration-resources.md#schriften-in-svg-text)); `registerBundleImages(bundle, { onWarning })` und `bundleImageUrl(bundle, { onWarning })` melden eine Familie ohne Schnitt.
- **`buildBundle(bundle)`** setzt die Kapitel der Reihe nach und gibt ein `VDTDocument` pro Kapitel zurück. Jedes Kapitel setzt das vorherige fort: Zähler für Überschriften und Ressourcen, den offenen Teil, die Seitenparität und die Seitennummerierung. Ein Kapitel, das das Inhaltsverzeichnis (`:::toc`) oder das Register (`:::index`) druckt, erhält die Gliederung des ganzen Buches. Die Funktion nimmt dieselben Optionen wie `buildDocument`, dazu `config`, um die Konfiguration des Pakets zu überschreiben, `cache`, um einen Mess-Cache zu teilen, und `metadata` (siehe unten).
- **`bundleResourceBytes(bundle)`** und **`bundleFontProvider(bundle, { decodeWoff2, fallback })`** sind die Optionen `resourceBytes` und `fontProvider` von `renderToPdf` aus `postext-pdf`. Der Font-Provider wählt aus dem Paket die nächstgelegene Stärke des angefragten Stils. Für einen Schnitt `.woff2` braucht er `decompressWoff2`, und für eine Familie, die das Paket nicht mitbringt, ruft er `fallback` mit den Argumenten des Renderers auf, `request` eingeschlossen, und reicht weiter, was diese Funktion zurückgibt. Ein Fallback, der nur die Datei `latin` einer Familie abruft, druckt eine chinesische Familie, die das Paket nicht einbettet, als leere Kästen; einer, der mit Teilen antwortet, etwa `sliceFontProvider` unter [Chinesische, japanische und koreanische Schriften](https://postext.dev/de/docs/configuration-pdf-output.md#chinesische-japanische-und-koreanische-schriften), druckt sie vollständig.

```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);                        // one VDTDocument per chapter
const firstPage = renderPage(docs[0].pages[0], docs[0]); // a <canvas>

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

Um ein einzelnes Kapitel selbst zu setzen, übergeben Sie `bundle.chapters[i].markdown`, `bundle.resources` und `bundle.config` an `buildDocument`, wie bei jedem anderen Dokument.

**Die Metadaten des Buches.** Wie in der Sandbox ist die Frontmatter des ersten Kapitels die des Buches: `buildBundle` gibt ihr `title`, ihr `author` und den Rest an jedes Kapitel weiter, sodass Kolumnentitel mit `{title}` und `{author}` auf jeder Seite gelten und das `doc.metadata` jedes Kapitels sie trägt. Ein Frontmatter-Block am Anfang eines späteren Kapitels wird ignoriert: Er wird an seinen Zeilen `---` erkannt und ohne Parsen geleert, sodass YAML, das der Parser ablehnen würde, keinen Schaden anrichtet. `options.metadata` liefert Werte, die die Frontmatter nicht festlegt (die Frontmatter hat Vorrang). Auch die Seitenzahl des Buches erreicht jedes Kapitel: `{bookTotalPages}` druckt sie, während `{totalPages}` die Seiten des Kapitels zählt (siehe [Seitenzahl des ganzen Buchs](https://postext.dev/de/docs/configuration-headers-footers.md#seitenzahl-des-ganzen-buchs)).

```ts
const docs = buildBundle(bundle, { metadata: { author: 'A. Author' } });
docs[3].metadata.title;   // the first chapter's `title:`
```

**Wo das Buch beginnt.** Ein Paket kann einen Teil einer längeren Veröffentlichung enthalten: die Seiten 58 bis 61 einer Ausgabe, Kapitel 4 eines Lehrbuchs. Sein `start` gibt an, was davor kommt, in den Begriffen der `continuation` von `buildDocument`: die Seiten vor der ersten (`pageIndexOffset`, das bestimmt, auf welche Seite Seite 1 fällt, und damit die gespiegelten Ränder und die Kolumnentitel ungerader und gerader Seiten), die geltende Seitennummerierung (`pageNumbering`), die Zähler der Überschriften (`headings`), den offenen Teil (`part`) und die Zähler für Ressourcen, Lehrsätze, Fußnoten und Zeilen. `createBundle` schreibt es als `start` der `preset.json`, `openBundle` gibt es als `bundle.start` zurück, und `buildBundle` setzt das erste Kapitel damit, wie es `buildDocument({ markdown, continuation: start }, config)` täte, und verkettet die folgenden Kapitel von dort aus; `{bookTotalPages}` zählt auch die Seiten vor dem Buch mit. Ein Manifest ohne `start` wird wie bisher gelesen, und ein Leser, der das Feld nicht kennt, ignoriert es. `bookPageCount` gehört nicht dazu: Der Leser zählt die Seiten. In einem Paket mit mehreren Sprachen gibt `localized[…].start` einer Ausgabe einen eigenen Beginn.

```ts
const { bytes } = await createBundle({
  name: 'Field notes, chapter 4',
  markdown,
  config,
  // Page 58, an even page, in chapter 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'
```

### Live-Beispiel: ein Paket öffnen

Der Pen lädt ein zweikapiteliges Beispielbuch (`lantern.postext`, mit eigener Schrift, einer SVG-Abbildung und einer Tabelle) aus dem Repository. Er registriert die Schriften und Bilder des Pakets, setzt das Buch mit `buildBundle` und zeichnet jede Seite. *Make the PDF* rendert dieselben Dokumente mit `postext-pdf` und bettet dabei die Schriften des Pakets ein. Wählen Sie eine eigene `.postext`-Datei, etwa eine aus der Sandbox exportierte, um sie auf dieselbe Weise zu sehen.

> **Ausführbares Beispiel: Postext · ein .postext-Paket öffnen** — Eine .postext-Datei mit postext öffnen, das Buch setzen und auf Canvas und als PDF rendern. ([Quelltext](https://github.com/drnachio/postext/tree/main/docs/examples/open-bundle))

### Ein Paket erstellen

`createBundle` schreibt aus einem Dokument eine `.postext`-Datei: seine Kapitel, seine Konfiguration, seine Ressourcen und die Nutzdaten, auf die sie verweisen.

```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 },
});
```

| Eingabe | Bedeutung |
| --- | --- |
| `name`, `id`, `description`, `locale` | Die Metadaten des Manifests. `id` ist standardmäßig ein Slug von `name`. |
| `chapters` oder `markdown` | Das Buch, ein `{ title?, markdown }` pro Kapitel, oder ein einzelnes Dokument. |
| `config` | Die `PostextConfig`. Werte, die den Standardwerten entsprechen, bleiben aus dem Manifest heraus. |
| `resources` | Die Ressourcen. Ein Bild nennt seine Nutzdaten über `bitmap.fileId` / `svg.fileId` (und `svg.pdfFileId` für ein Druckoriginal). |
| `files` | Die Nutzdaten nach `fileId` (ein Objekt oder eine `Map`): die Bilder, auf die die Ressourcen verweisen, und die Schriftdateien, auf die die Varianten von `config.customFonts` verweisen. Werte können ein `Uint8Array`, ein `ArrayBuffer`, ein `Blob` oder ein String (SVG-Markup) sein. |
| `thumbnail` | `{ data, mime }`: ein Umschlagbild (PNG, JPEG, WebP, GIF oder SVG). |
| `canvasScope` | `'book'` bittet Betrachter, das ganze Buch als einen Canvas zu setzen. |
| `start` | Wo das Buch beginnt, für ein Paket, das einen Teil eines längeren enthält: die `continuation`, mit der ein einzelnes Dokument gesetzt würde. Wird als `start` des Manifests geschrieben. |
| `mtime` | Das Änderungsdatum, das in jede Datei des Archivs geschrieben wird (ein `Date`, ein Zeitstempel oder ein Datumsstring). Fehlt es, ist es die Zeit des Aufrufs, sodass zwei Aufrufe mit derselben Eingabe unterschiedliche Bytes ergeben. Übergeben Sie ein festes Datum, ergibt dieselbe Eingabe dieselben Bytes, die Sie hashen oder vergleichen können. Ein Zip speichert Datum und Uhrzeit ohne Zeitzone, in Schritten von zwei Sekunden, von 1980 bis 2099, und das Datum wird in der Ortszeit des Rechners geschrieben. Für Bytes, die auf jedem Rechner übereinstimmen, bilden Sie das Datum aus lokalen Feldern, etwa `new Date(1980, 0, 1)`: Ein Zeitstempel oder ein String, der auf `Z` endet, bezeichnet einen Zeitpunkt, der in jeder Zeitzone auf eine andere Ortszeit fällt (`'1980-01-01T00:00:00Z'` ist westlich von UTC noch 1979). Ein Datum außerhalb dieser Jahre, in Ortszeit, wirft einen Fehler. |
| `localized` | Weitere Sprachen desselben Buches, nach Sprach-Tag: `{ es: { name?, description?, chapters?, config?, resources? } }`. Die obigen Eingaben sind dann der Inhalt von `locale`, das in diesem Fall erforderlich ist. Siehe [Zweisprachige Pakete](https://postext.dev/de/docs/configuration-bundles.md#zweisprachige-pakete). |

Die Funktion gibt die `bytes` des Archivs zurück, das als `preset.json` geschriebene `manifest`, jede Datei als `files` (Pfad → Bytes) und eine Liste von `warnings`. Dateien werden nach der ID ihrer Ressource (`resources/lantern.svg`) oder dem Namen ihrer Schriftdatei (`fonts/…`) benannt, Kapitel nach ihrer Reihenfolge und ihrem Titel (`chapters/01-dusk.md`). Schriften werden in den `fonts` des Manifests deklariert, nie in `config.customFonts`. Manches bleibt draußen, jeweils mit einer Warnung:
- eine Ressource oder ein Schnitt, deren Nutzdaten nicht in `files` stehen
- ein Schnitt `.woff` (das PDF-Backend kann ihn nicht einbetten)
- eine Familie mit der Markierung `redistributable: false`

Übergeben Sie `bytes` im Browser an einen Download-Link: `URL.createObjectURL(new Blob([bytes], { type: 'application/zip' }))`. In Node schreiben Sie sie mit `fs.writeFile`. `createBundle` und `openBundle` brauchen kein DOM. Die Distributionen verwenden Modulpfade ohne Dateiendung, deshalb brauchen sie unter reinem Node, ohne Bundler, einen Resolve-Hook. `docs/examples/open-bundle/build-sample.mjs` im Repository zeigt einen in wenigen Zeilen.

### Zweisprachige Pakete

Eine `.postext`-Datei kann ein Buch in mehreren Sprachen enthalten, und `openBundle(bytes, { locale })` liest es in jeder davon. `createBundle` schreibt eine solche Datei aus `localized`: ein Eintrag pro zusätzlicher Sprache, jeweils mit dem, was sich vom primären Inhalt (der Eingabe `locale`) unterscheidet:

```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' });   // Spanish chapters, config and captions
```

- **`chapters`**: das Buch in dieser Sprache. Die Kapiteldateien kommen in einen Ordner pro Sprache (`chapters/en/01-dusk.md`, `chapters/es/01-anochecer.md`), und die `chapters` des Manifests werden zu einer Zuordnung Sprache → Kapitel. Eine Sprache ohne `chapters` liest die primären; hat keine Sprache eigene Kapitel, bleiben sie eine einzige Liste.
- **`config`**: die Konfiguration für diese Sprache. Jeder Schlüssel der obersten Ebene ersetzt den gemeinsamen Schlüssel vollständig, wenn das Paket in dieser Sprache gelesen wird, sodass `headings` oben das ganze Objekt `headings` ersetzt. Weggelassene oder mit den gemeinsamen übereinstimmende Schlüssel werden geteilt und nicht geschrieben; die vollständige Konfiguration der Sprache zu übergeben funktioniert also ebenso gut wie die wenigen Schlüssel, die sich ändern. Ein Schlüssel, der auf seine Standardwerte gesetzt ist, während der gemeinsame es nicht ist (`layout: {}`), wird so geschrieben, wie er übergeben wurde, und setzt damit den gemeinsamen Wert zurück. Schriften werden geteilt: Die Familien aus den `customFonts` einer Sprache kommen zu den `fonts` des Pakets hinzu.
- **`resources`**: der Wortlaut der gemeinsamen Ressourcen, zugeordnet über `id`: `caption`, `note`, `altText` und die `table` einer Tabelle. Ein Bild mit Wörtern darin kann eine eigene Grafik haben: `bitmap.fileId` oder `svg.fileId` (und `svg.pdfFileId`) nennen andere Nutzdaten in `files`, geschrieben als `resources/es/lantern.svg`. Andere Felder, etwa der Typ oder die Platzierung, werden geteilt. Eine ID, die nicht unter `resources` vorkommt, bleibt mit einer Warnung draußen, und ein fehlendes sprachspezifisches Bild belässt die Sprache beim gemeinsamen, ebenfalls mit einer Warnung.
- **`name`**, **`description`**: Titel und Beschreibung des Buches in dieser Sprache, geschrieben als `localized[…].name` und `description`, wenn sie von den gemeinsamen abweichen. `openBundle` gibt sie für diese Sprache als `bundle.name` und `bundle.description` zurück, die Sandbox führt die Ausgabe unter diesem Titel (beim Import und in ihrer Bibliothek), und `postext info` und `postext check` geben sie aus. Ein Eintrag in der `index.json` einer Preset-Quelle kann sie unter `localized` übernehmen (`localized.ca.name`), damit die Bibliothek jede Ausgabe schon vor dem Öffnen mit ihrem Titel zeigt. Eine Sprache ohne sie behält `name` und `description` des Manifests.

Das Manifest listet jede Sprache in `locales` auf (`['en', 'es']`), behält die primäre als `locale` und speichert die übrigen unter `localized`. `openBundle` ohne Sprache liest die primäre Sprache.

**Welche Sprache ein Leser erhält.** `openBundle(bytes, { locale })` liefert die genaue Sprache, sonst ihre Grundsprache (`es-MX` liest `es`), sonst die primäre, und `bundle.locale` gibt an, welche geliefert wurde. Kapitel und Wortlaut stammen immer aus derselben Sprache. Die primäre Sprache behält den gemeinsamen Wortlaut, auch wenn `localized` eine regionale Variante davon enthält: Ein Paket `pt-PT` mit einem Eintrag `pt-BR` liest die brasilianischen Legenden nur für `pt-BR` und die gemeinsamen für `pt-PT` und `pt`.

### Live-Beispiel: ein Paket erstellen

Der Pen baut ein zweikapiteliges Buch mit einer SVG-Abbildung und listet die Dateien auf, die `createBundle` geschrieben hat, zusammen mit dem Manifest. Er bietet das Archiv zum Download an, öffnet es dann mit `openBundle` wieder und zeichnet seine erste Seite: der vollständige Hin- und Rückweg in wenigen Zeilen. Importieren Sie die heruntergeladene Datei in die Sandbox, um dort daran weiterzuarbeiten.

> **Ausführbares Beispiel: Postext · ein .postext-Paket erstellen** — Eine .postext-Datei mit createBundle von postext schreiben, herunterladen und wieder öffnen. ([Quelltext](https://github.com/drnachio/postext/tree/main/docs/examples/create-bundle))

### Mit Paketen arbeiten

Da die Sandbox, der Agenten-Skill und das Paket `postext` alle dieselbe Datei lesen und schreiben, ist eine `.postext`-Datei ein bequemer Weg, ein Buch von einem Werkzeug an ein anderes weiterzugeben:

- **Mit einem Paket beginnen.** Portieren Sie eine bestehende Veröffentlichung mit dem [Agenten-Skill](https://postext.dev/de/docs/skill.md), oder gestalten Sie ein Buch in der [Sandbox](https://postext.dev/de/sandbox.md) und exportieren Sie es (**Herunterladen (.postext)** im Menü ⋯ seiner Zeile im Bedienfeld Bücher). Laden Sie die Datei in Ihrem Programm mit `openBundle`, um sie auf Canvas, als HTML oder als PDF zu rendern. Behalten Sie die Datei als Quelle des Buches: Bearbeiten Sie die Kapitel, die Konfiguration oder die Ressourcen im Code und schreiben Sie sie mit `createBundle` zurück, oder laden Sie sie einfach neu, wann immer sie sich ändert.
- **In der Sandbox Fehler suchen und feinabstimmen.** Wenn etwas in der Ausgabe Ihres Programms Nacharbeit braucht (eine Abbildung, die auf der falschen Seite landet, ein Überschriftenformat, der Spaltenausgleich), exportieren Sie mit `createBundle`, was Ihr Programm setzt. Importieren Sie diese Datei in die Sandbox (*Bücher → Neu → Eine .postext-Datei öffnen …*), korrigieren Sie Text, Gestaltung oder Abbildungen mit der Live-Vorschau, dem Bedienfeld Prüfungen und der PDF-Ansicht, und exportieren Sie sie dann erneut. Ihr Programm lädt danach die korrigierte Datei mit `openBundle`. Oder übernehmen Sie die Änderungen zurück in Ihren Code: Die `config` des Manifests enthält nur die Werte, die von den Standardwerten abweichen, und liest sich deshalb wie ein kurzer Diff.

### API der unteren Ebene

`postext/bundle` exportiert außerdem die Bausteine hinter `openBundle` und `createBundle`, für Hosts, die Pakete auf eigene Weise speichern oder ausliefern (ein entpacktes Verzeichnis über HTTP, Datensätze in einer Datenbank):

- **`openBundleZip(bytes)` / `zipBundle(files, { mtime })`**: die Archivschicht. Beim Öffnen wird ein Ordner der obersten Ebene toleriert, und Einträge `__MACOSX` sowie Dotfiles werden ignoriert. Pfade, die aus dem Paket hinausführen, werden abgewiesen. `mtime` datiert die Dateien wie die gleichnamige Eingabe von `createBundle`.
- **`readBundle(manifest, readFile, options)`** liest ein Manifest und einen Callback `readFile(path)` zu Kapiteln, Konfiguration, Ressourcen, Bildern und Schriften ein. `options` legt die Sprache fest, wie Datei-IDs benannt werden (`ids`), die Basiskonfiguration (`baseConfig`, unter der des Manifests; standardmäßig die Palette und die Ressourcentypen von `bundleBaseConfig` in der Sprache des Pakets, die `resolveBundleConfigLocale(manifest, locale)` zurückgibt, und ein Host, der eine eigene `baseConfig` übergibt, sollte sie in diese Sprache lokalisieren; unter einem Manifest älter als `configVersion: 4` wird ihre `math` mit der des Pakets fixiert, unter einem älter als 5 der Inline-Abstand ihres `layout`, unter einem älter als 6 der Abstand in Kästen ihres `layout`, der Raum unter der Doppelpunktzeile ihres `bodyText`, die Auszeichnungen in ihren `headings` und die Größen ihrer Initialen, unter einem älter als 7 die Umbrüche an Strichen und der Umbruch im Flattersatz ihres `bodyText` und unter einem älter als 8 die Teilung unter einer Überschrift ihrer `headings` sowie die Umbrüche an Komposita und der Raum unter Containern `:::paragraphs` ihres `bodyText`, siehe [Pakete, die postext 1.4 oder früher geschrieben hat](https://postext.dev/de/docs/configuration-bundles.md#pakete-die-postext-14-oder-früher-geschrieben-hat)) und wie Eigengrößen gemessen werden. `readResolution` liest die Auflösung jeder Bitmap aus ihrer Datei in `bitmap.fileResolution`; der Standardwert ist true, wenn das Paket `layout.bitmapResolution: 'file'` festlegt.
- **`planBundle(meta, content)` / `resolveBundleFiles(plan, sources)`**: die Schreibseite, aufgeteilt in einen reinen Plan (Dateinamen und Manifest) und das Auflösen der Bytes über die Callbacks `readBlob` / `readFont`.
- **`isBundleManifest(value)`**, die Sprachwähler (`pickChapterSpecs`, `pickLocaleOverrides`, `pickBundleView`, `resolveBundleLocale`, `resolveBundleConfigLocale`), `svgSize` / `bitmapSize` / `bitmapInfo` (die Pixel einer Bitmap und die Auflösung, die ihre Datei angibt) und die Formattypen (`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)` und `LEGACY_MATH_SIZE`** (0,5 ÷ 0,442): eine gespeicherte Konfiguration in den Begriffen von heute (siehe [Pakete, die postext 1.4 oder früher geschrieben hat](https://postext.dev/de/docs/configuration-bundles.md#pakete-die-postext-14-oder-früher-geschrieben-hat)). `readBundle` wendet das an; ein Host, der Konfigurationen auf eigene Weise speichert, kann das ebenfalls tun, einmal pro gespeicherter Kopie.

Die Sandbox baut darauf auf. Sie ergänzt eigene Speicher-IDs und die Seitendatensätze `layouts.json`.
