# الاستخدام البرمجي: حزم .postext

> فتح ملفات .postext وإخراجها ورسمها وإنشاؤها من الشيفرة، والحزم الثنائية اللغة، وملفات الإصدارات القديمة، والواجهة البرمجية منخفضة المستوى

- نسخة HTML: https://postext.dev/ar/docs/configuration-bundles
- آخر تحديث: 2026-10-16
- مدة القراءة: 27 دقيقة
- لغات أخرى: [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), [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)

## باختصار

هذه الصفحة لمن يكتبون الشيفرة. يحمل ملف .postext كتابًا كاملًا في ملف واحد: نصه وإعداداته وخطوطه وصوره. وتبيّن الصفحة كيف تفتح هذا الملف، وتُخرج صفحاته، وترسمها. وتبيّن كيف تصنع ملفًا جديدًا من نصك، وكيف تحفظ لغتين من الكتاب في ملف واحد. وتشرح أيضًا كيف تُقرأ الملفات التي صنعتها إصدارات أقدم من Postext.

## الحزم (ملفات `.postext`)

ملف **`.postext`** كتابٌ كامل في ملف واحد: أرشيف zip يضم ملف بيان (manifest) اسمه `preset.json`، وملف Markdown لكل فصل، وملفات الموارد (صور نقطية، وملفات SVG، ونسخ PDF الأصلية للطباعة (print masters))، وملفات الخطوط التي تسمّيها الإعدادات. يصدّره [Sandbox](https://postext.dev/ar/docs/sandbox-books-saving.md#التصدير-والاستيراد) ويستورده، وتسلّمه [مهارة الوكيل](https://postext.dev/ar/docs/skill.md) (agent skill). وتستطيع مكتبة `postext` أيضًا إنشاءه وفتحه، فينتقل الكتاب بين هذه الأدوات وبرنامجك دون أن يفقد شيئًا.

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

يصف ملحق [صيغة حزمة الإعداد المسبق](https://postext.dev/ar/docs/sandbox-embedding.md#صيغة-حزمة-الإعداد-المسبق) في توثيق Sandbox البيانَ حقلًا حقلًا. وقد يحمل الملف أيضًا `layouts.json`، أي أعداد الصفحات التي حسبها Sandbox، فيُفتح الكتاب هناك مقسَّمًا إلى صفحات من البداية، أو ملفًا لكل طبعة من كتاب متعدد اللغات (`layouts.zh-Hant.json`، ويُقرأ أولًا). ويتجاهلها `openBundle`.

تُصدَّر الواجهة البرمجية من `postext` نفسها ومن المسار الفرعي `postext/bundle`، الذي يضيف الأدوات المساعدة منخفضة المستوى. استورد من `postext` إذا كنت سترسم أيضًا. بهذا تتشارك محوّلات الحزمة (adapters) والمُخرِجات نسخة واحدة من الوحدة، وهذا مهم على شبكة توزيع محتوى (CDN) مثل esm.sh، حيث تُبنى كل نقطة دخول على حدة.

### فتح حزمة

تأخذ `openBundle` بايتات الملف (`Uint8Array` أو `ArrayBuffer` أو `Blob` / `File` من `<input type="file">`) وتعيد كل ما يحتاجه المحرّك ومُخرِجاته (backends):

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

| الحقل | ما يحتويه |
| --- | --- |
| `manifest` | ملف `preset.json` بعد التحقق من صحته. |
| `id`، `name`، `description` | من البيان. و`name` و`description` هما ما للطبعة المقروءة: `localized[…].name` و`description` للغة المقدَّمة إن ذكرتهما، وإلا فما في البيان. |
| `locale`، `locales` | اللغة التي قُرئ بها المحتوى، وكل لغة تحملها الحزمة الثنائية اللغة. يختار `options.locale` إحداها: الوسم المطابق تمامًا أولًا، ثم اللغة دون متغيرها الإقليمي، ثم لغة الحزمة نفسها. |
| `chapters` | `{ title, file, markdown }` لكل فصل. الفصل الذي لا عنوان له في البيان يأخذ نص أول عنوان `#` فيه. |
| `config` | لوحة الألوان الافتراضية وأنواع الموارد بلغة الحزمة، ثم `config` الموجود في البيان، ثم تجاوزات اللغة. لغة الحزمة هي `locale` أعلاه، فالحزمة المكتوبة بلغة واحدة تنال تسمياتها الخاصة أيًّا كان ما يطلبه `options.locale`. والبيان الذي لا يسمّي لغة يأخذ اللغة التي يضبطها `config` الخاص به (`locale`، ثم لغة تقسيم الكلمات بالواصلة)، وإلا فـ`options.locale`. ويسرد `customFonts` عائلات الخطوط التي في الحزمة. هذه هي الإعدادات نفسها التي يفتح بها Sandbox الحزمة. |
| `resources` | الموارد، مع تعليقات اللغة المختارة. المقاس الناقص من البيان يُقرأ من الملف. |
| `fonts` | مُدخل واحد لكل وجه خط: `{ family, weight, style, format, file, bytes }`. |
| `files` | كل ملف في الأرشيف، ومفتاحه مساره. |
| `thumbnail`، `canvasScope` | مسار صورة الغلاف، وطريقة العرض التي تطلبها الحزمة: الحقل `view` في البيان، وفوقه `localized[…].view` الخاص باللغة المقدَّمة. |
| `start` | موضع بداية الكتاب، في حزمة تضمّ جزءًا من كتاب أطول: `start` في البيان، أو `localized[…].start` للغة المقروءة. لا يوجد في كتاب يبدأ من أوله. |
| `warnings` | مشكلات لا توقف القراءة: ملف خط غير مدعوم، أو نسخة طباعة أصلية مفقودة. |

**معرّف `fileId` لأي ملف هو مساره داخل الحزمة**. يمكن البحث عن `resource.svg.fileId` و`resource.bitmap.fileId` و`fileId` الخاص بكل متغير في `customFonts` مباشرةً في `bundle.files`. ترمي `openBundle` خطأً إذا لم تكن البايتات أرشيف zip، أو لم يوجد `preset.json` صالح (في الجذر أو تحت مجلد واحد في المستوى الأعلى)، أو كان ملف يسمّيه البيان مفقودًا.

#### الحزم التي كتبها postext 1.4 أو ما قبله

كل بيان يكتبه `createBundle` أو Sandbox يحمل `configVersion: 14`: قواعد الإعدادات التي كُتب لها `config` الخاص به. والبيان الذي يخلو منه كتبه postext 1.4 أو ما قبله، وكان يضبط ثمانية عشر أمرًا على نحو مختلف:

- **فواصل العناوين** (القواعد 3): حتى 1.4، كان كائن `headings` الذي لا فاصل فيه لـH1 بلا أي فاصل (انظر [التجاوزات حسب المستوى](https://postext.dev/ar/docs/configuration-headings.md#تجاوزات-كل-مستوى)).
- **حجم الصيغ الرياضية** (القواعد 4): حتى 1.4، كانت الصيغ تخرج أكبر بـ1.131 مرة مما يقوله `fontSizeScale` (انظر [حجم الصيغ](https://postext.dev/ar/docs/configuration-lists-math.md#الصيغ-الرياضية)).
- **الفراغ تحت المورد المضمَّن** (القواعد 5): حتى 1.4، كان النص الذي يلي شكلًا أو جدولًا موضعه `placement.position: 'here'` يُستأنف عند خط الشبكة التالي، دون فراغ العناصر العائمة تحته (انظر `layout.inlineResourceGap` في [تخطيط الصفحة](https://postext.dev/ar/docs/configuration-page-layout.md#التخطيط)).
- **الفراغ حول المورد المضمَّن داخل إطار** (القواعد 6): حتى 1.4، كان مثل هذا المورد يلتصق بنص الإطار المحيط به (انظر `layout.inlineResourceGapInBoxes` في [تخطيط الصفحة](https://postext.dev/ar/docs/configuration-page-layout.md#التخطيط)).
- **العلامات المضمَّنة في العناوين** (القواعد 6): حتى 1.4، كان العنوان يطبع كلمات `*italic*` و`**bold**` وسائر علاماته بنمطه العادي الخاص (انظر `headings.inlineMarks` في [العناوين](https://postext.dev/ar/docs/configuration-headings.md#العناوين)).
- **حجم الحرف الاستهلالي** (القواعد 6): حتى 1.4، كان `dropCap` في نص تصميمي بلا `fontSize` بطول جميع صناديق الأسطر التي يمتد عليها، وأعلاه فوق السطر الأول (انظر `dropCap` في [عناصر النص](https://postext.dev/ar/docs/configuration-headers-footers.md#عناصر-النص)).
- **المساحة تحت سطر النقطتين** (القواعد 6): حتى 1.4، كان `keepColonWithList` يعدّ سطرًا واحدًا من المساحة تحت السطر المنتهي بنقطتين كافيًا للقائمة، فينتقل البند الأول ذو السطرين، الذي تُبقيه قواعد اليتيمة والأرملة كاملًا، إلى العمود التالي من دونه (انظر `bodyText.colonListRoom`).
- **الأسطر التي يتركها قطع الإطار** (القواعد 6): حتى 1.4، كان الإطار الذي ينقسم داخل فقرة أو بند قائمة قد يترك سطرًا واحدًا منها على أحد الجانبين، ما دام كل جانب من الإطار يضم أسطره `splitMinLines` إجمالًا (انظر `layout.boxChildSplitMinLines` في [تخطيط الصفحة](https://postext.dev/ar/docs/configuration-page-layout.md#التخطيط)).
- **فواصل الأسطر عند الشرطة** (القواعد 7): حتى 1.4، لم يكن Knuth-Plass ينهي سطرًا أبدًا بعد شرطة طويلة أو متوسطة ملتصقة بين كلمتين (`say—that’s`)، ولم يكن مقسِّم الأسطر سطرًا بسطر للنص المنسَّق ينهيه إلا بين حرفين (انظر `bodyText.breakAfterDashes` في [النص الأساسي](https://postext.dev/ar/docs/configuration-text.md#نص-المتن)).
- **النص غير المضبوط** (القواعد 7): حتى 1.4، كان النص الجاري غير المضبوط يُنضَّد سطرًا بسطر، فيُملأ كل سطر قبل الذي يليه، أيًّا كانت قيمة `optimalLineBreaking` (انظر `bodyText.optimalRagged` في [النص الأساسي](https://postext.dev/ar/docs/configuration-text.md#نص-المتن)).
- **التقسيم تحت العنوان** (القواعد 8): حتى 1.4، كانت الفقرة التي تلي عنوانًا في أسفل عمود تُبقي هناك كل ما يتسع من أسطرها، مهما قلّ ما ينتقل منها إلى العمود التالي (انظر `headings.keepWithNextSplit` في [العناوين](https://postext.dev/ar/docs/configuration-headings.md#العناوين)).
- **الفراغ تحت حاوية `:::paragraphs`** (القواعد 8): حتى 1.4، كان فراغ النمط يوضع تحت الفقرة الأخيرة قبل المحاذاة إلى الشبكة، ويُضاف تحته فراغ الكتلة التالية من أعلاها (`marginTop` الخاص بالعنوان)، ويُهمَل تباعد الفقرات في النص (انظر `bodyText.paragraphContainerSpacing` في [النص الأساسي](https://postext.dev/ar/docs/configuration-text.md#نص-المتن)).
- **فواصل الأسطر عند واصلة الكلمة المركبة** (القواعد 8): حتى 1.4، لم يكن Knuth-Plass ينهي سطرًا مضبوطًا أبدًا بعد واصلة بين حرفين (`well-known`) في فقرة بلا تنسيق مضمَّن، بينما كان يفعل ذلك في فقرة فيها تنسيق (انظر `bodyText.breakAfterHyphens` في [النص الأساسي](https://postext.dev/ar/docs/configuration-text.md#نص-المتن)).
- **القصائد بلا فاصل** (القواعد 9): حتى 1.22 كانت قصيدة `:::verse` التي لا تحمل أسطرها `||` تُصَفّ أشطرًا مفردة، كل سطر في الوسط (انظر `bodyText.verse.layout` في [الشعر](https://postext.dev/ar/docs/configuration-text.md#الشعر)).
- **إزاحة السطر الأول بجانب إزاحة معلّقة** (القواعد 9): حتى 1.22 كانت `hangingIndent` في نمط الفقرة تحلّ محل `firstLineIndent`، فيبدأ السطر الأول عند `indent` (انظر [أنماط الفقرات](https://postext.dev/ar/docs/configuration-styles.md#أنماط-الفقرات)).
- **الشرطة المائلة العكسية في آخر السطر** (القواعد 9): حتى 1.22 كانت الشرطة المائلة العكسية في آخر سطر من فقرة أو اقتباس أو عنصر قائمة، و`\\` المتبوعة بمسافة، تُطبع كما هي، وتُوصل الأسطر بمسافة (انظر `bodyText.hardLineBreaks` تحت [نص المتن](https://postext.dev/ar/docs/configuration-text.md#نص-المتن)).
- **أسوار الشيفرة** (القواعد 9): حتى 1.22 كان سور ```` ``` ```` أو `~~~` والأسطر داخله تُقرأ Markdown: تندمج الأسطر في فقرات، ويصير السطر الذي يبدأ بـ`#` عنوانًا، وتُطبع الأسوار (انظر `codeStyle.blocks` تحت [قوائم الشيفرة](https://postext.dev/ar/docs/configuration-styles.md#قوائم-الشيفرة)).
- **تتمّات أسطر الشعر** (القواعد 10): في 1.23 كان سطر القصيدة المصفوفة سطرًا سطرًا الأعرض من عرض السطر يُكمَل في سطر تالٍ بتباعده الطبيعي بين الكلمات، مهما قلّ ما زاد منه (انظر `bodyText.verse.tighten` تحت [الشعر](https://postext.dev/ar/docs/configuration-text.md#الشعر)).

يقرأ `openBundle` و`readBundle` الحقل `config` في بيان كهذا، وإعدادات كل لغة في `localized`، عبر `migrateConfig`، التي تكتب صراحةً فواصل العناوين كما أخرجها الإصدار 1.4، وتضرب مقياس الصيغ في 1.131 (وتقسم عليه هوامش الصيغ المعروضة المعبَّر عنها بوحدة `em`). أما البيان الموسوم بقيمة من `3` إلى `7`، الذي كتبه إصدار تجريبي من 1.5، فلا يتلقى إلا تثبيتات (pins) القواعد اللاحقة لوسمه. مع `3` يعني ذلك حجم الصيغ، والفراغ تحت المورد المضمَّن، وتثبيتات القواعد 6 الخمسة، وتثبيتَي القواعد 7، وتثبيتات القواعد 8 الثلاثة؛ ومع `4`، الفراغ تحت المورد المضمَّن وتثبيتات القواعد 6 و7 و8؛ ومع `5`، تثبيتات القواعد 6 و7 و8؛ ومع `6`، تثبيتات القواعد 7 و8؛ ومع `7`، تثبيتات القواعد 8 وحدها. يُكتب التقسيم تحت العنوان (`pinLegacyHeadingSplit`) على شكل `headings.keepWithNextSplit: 'fill'` في كائن `headings` الذي تُبقيه الطبقات ساريًا، إذا كان في أحد الفصول المقروءة عنوان، ولم تحدد الإعدادات قيمة خاصة بها، وأبقت `headings.keepWithNext` مفعَّلًا، ولم تعطّل `bodyText.avoidOrphans`. وتُكتب فواصل الكلمات المركبة (`pinLegacyHyphenBreaks`) على شكل `bodyText.breakAfterHyphens: false` في `bodyText` الساري، إذا وضع أحد الفصول المقروءة واصلة بين حرفين، ولم تكن الإعدادات تضبطه أصلًا ولا تعطّل `optimalLineBreaking`. ويُكتب الفراغ تحت الحاويات (`pinLegacyParagraphContainerSpacing`) على شكل `bodyText.paragraphContainerSpacing: 'add'` في `bodyText` الساري، إذا عرّفت الإعدادات نمط فقرة (في `paragraphStyles` أو في تجاوزات عارض HTML)، وفتح أحد الفصول المقروءة حاوية `:::paragraphs` في سطر مستقل، ولم تكن الإعدادات تضبطه أصلًا. وتُكتب فواصل الشرطات (`pinLegacyDashBreaks`) على شكل `bodyText.breakAfterDashes: false` في `bodyText` الساري، إذا وضع أحد الفصول المقروءة شرطة طويلة أو متوسطة ملتصقة بين كلمتين (قبلها حرف أو رقم أو علامة ترقيم إغلاق، وبعدها حرف أو رقم أو قوس فتح أو علامة اقتباس فتح؛ وتُحتسب علامة الاقتباس التي قبلها إذا سبقها حرف أو رقم أو علامة ترقيم إغلاق أو مسافة غير قابلة للكسر، كما في `"no"—and`، لا في `said "—Hola`؛ وتُحتسب علامة التنسيق المضمَّنة الملاصقة للشرطة أو لعلامة الاقتباس، مثل `**` في `**riddles.**—I`، من أي الجانبين)، ولم تكن الإعدادات تضبطه أصلًا. ويُكتب تقسيم الأسطر غير المضبوطة (`pinLegacyRaggedBreaking`) على شكل `bodyText.optimalRagged: false` في `bodyText` الساري، إذا جعلت الإعدادات بعض النص الجاري غير مضبوط (قيمة `textAlign` غير `'justify'` على النص الأساسي، أو على نمط فقرة، أو متن إطار، أو متن جزء، أو متن نمط قسم (`headingStyles[].bodyStyle`)، أو في تجاوزات عارض HTML)، ولم تكن تضبطه أصلًا، ولا تعطّل `optimalLineBreaking`. ويُكتب الفراغ في الأطر (`pinLegacyBoxResourceGap`) على شكل `layout.inlineResourceGapInBoxes: false` في `layout` الساري، إذا ضُمِّن مورد في سطر مستقل داخل `:::callout` في الفصول المقروءة، ولم تكن الإعدادات تضبطه أصلًا. ويُكتب قطع الإطار (`pinLegacyBoxChildCut`) على شكل `layout.boxChildSplitMinLines: 1` في `layout` الساري، إذا فتح أحد الفصول المقروءة `:::callout` في سطر مستقل، ولم تكن الإعدادات تضبطه أصلًا. وتُكتب علامات العناوين (`pinLegacyHeadingMarks`) على شكل `headings.inlineMarks: false` في `headings` الذي تُبقيه الطبقات ساريًا، إذا حمل عنوانٌ في الفصول المقروءة علامةً (`*` أو `_` أو `^` أو `~` أو `:smallcaps[` أو رابطًا في نصه) ولم تحدد الإعدادات قيمة خاصة بها. أما الحروف الاستهلالية (`pinLegacyDropCapSize`) فيُكتب حجمها في 1.4 صراحةً على شكل `dropCap.fontSize`، أينما كانت: بوحدة تباعد أسطر العنصر إن كان طولًا، وإلا فبوحدة حجم خطه. وتُكتب المساحة تحت سطر النقطتين (`pinLegacyColonListRoom`) على شكل `bodyText.colonListRoom: 'line'` في `bodyText` الساري، إذا جاءت قائمة في الفصول المقروءة بعد سطر ينتهي بنقطتين (ويُسمح بأسطر فارغة بينهما)، ولم تحدد الإعدادات مساحة ولا عطّلت `keepColonWithList`. ويُكتب الفراغ تحت المورد المضمَّن (`pinLegacyInlineGap`) على شكل `layout.inlineResourceGap: 'above'` في `layout` الذي تُبقيه الطبقات ساريًا، إذا ضمَّن سطرٌ من الفصول المقروءة موردًا (`::resource{id="…"}` وحده في سطره، كما يقرؤه المحلِّل: ذكره في النص الجاري أو في مقطع شيفرة لا يُحتسب) ولم تحدد الإعدادات فراغًا خاصًا بها. ويُثبَّت الحجم على `math` الذي تُبقيه الطبقات ساريًا (إذ يحلّ `math` الخاص بلغةٍ ما محلّ المشترك)، ولا يُثبَّت إلا إذا كان في الفصول المقروءة الرمز `$`: فالحزمة التي لا صيغ فيها تحتفظ بـ`config` كما كُتب. وإذا لم يحدد البيان ولا اللغة `math`، فالساري هو `baseConfig` الخاص بـ`readBundle` (إعدادات القارئ نفسه)، ويُثبَّت هو أيضًا، لأن 1.4 كان ينضّد صيغ الحزمة بذلك الحجم: فالقيمة الأساسية `fontSizeScale: 1.5` تُقرأ 1.5 × 1.1312. أما فواصل العناوين في الإعدادات الأساسية فتؤخذ كما هي. وهكذا تحتفظ الحزمة القديمة بما أخرجته هذه القواعد، ويُظهر `bundle.config` ما تُخرَج به من فواصل، وحجم صيغ، وفراغات، وعلامات عناوين، وأحجام حروف استهلالية، ومساحة تحت سطر النقطتين، وقطع إطار، وفواصل شرطات، وفواصل كلمات مركبة، وتقسيم للأسطر غير المضبوطة، وتقسيم تحت العنوان، وفراغ تحت الحاويات. أما إصلاحات الإخراج في 1.5 فلا تثبيت لها، وتسري عليها كما تسري على أي كتاب، لذا قد تتحرك صفحة تمسّها (انظر [حجم الصيغ](https://postext.dev/ar/docs/configuration-lists-math.md#الصيغ-الرياضية) للاطلاع على القائمة). ملف `preset.json` المكتوب يدويًا لقواعد اليوم يضبط `"configVersion": 14`؛ ووسم بيان حزمة قديمة بهذه القيمة هو أيضًا الطريقة ذات السطر الواحد لقراءتها بقواعد اليوم (وعندها تفقد الحزمة التي لا إصدار لها تثبيت فواصل العناوين أيضًا). والبيان الموسوم `8`، الذي كتبه postext من 1.5 إلى 1.22، يتلقى التثبيتات الأربعة التي في القواعد 9 وحدها، كما يتلقاها كل بيان أقدم: يُكتب تخطيط الشعر (`pinLegacyVerseLayout`) بصفته `bodyText.verse.layout: 'bayt'` على `bodyText` النافذ، حين يصفّ فصل مقروء قصيدة `:::verse` لا يسمّي سطر فتحها تخطيطًا ولا تحمل أسطرها فاصل شطرين، ولا تحدّده الإعدادات بعد؛ وتحذف الإزاحات المزدوجة (`pinLegacyPairedIndents`) قيمة `firstLineIndent` من كل نمط فقرة (في `paragraphStyles` أو في تجاوزات عارض HTML) يحدّد أيضًا `hangingIndent` غير صفرية؛ وتُكتب فواصل الأسطر الإجبارية (`pinLegacyHardBreaks`) بصفتها `bodyText.hardLineBreaks: false` على `bodyText` النافذ، حين يختم فصل مقروء سطرًا من فقرة أو اقتباس أو عنصر قائمة بشرطة مائلة عكسية والكتلة مستمرة تحته، أو يضع `\\` متبوعة بمسافة ونص (ما عدا الشيفرة والصيغ المضمّنة، والصيغ المعروضة، والعناوين، وقصائد `:::verse`)، ولا تحدّده الإعدادات بعد؛ وتُكتب أسوار الشيفرة (`pinLegacyCodeBlocks`) بصفتها `codeStyle.blocks: false`، حين يفتح فصل مقروء سور ```` ``` ```` أو `~~~` (من ثلاث علامات أو أكثر، بإزاحة لا تزيد على ثلاث مسافات) ولا تحدّده الإعدادات بعد. والبيان الموسوم `9`، الذي كتبه postext 1.23، لا يتلقى إلا تثبيت القواعد 10، كما يتلقاه كل بيان أقدم أيضًا: تُكتب تتمّات أسطر الشعر (`pinLegacyVerseTightening`) بصفتها `bodyText.verse.tighten: false` في `bodyText` الساري، حين يصفّ فصل مقروء قصيدة سطرًا سطرًا (سطر فتح `:::verse` يسمّي `layout=lines`، أو لا يسمّي تخطيطًا وأسطره بلا فاصل بين الشطرين ما دامت الإعدادات لا تصفّ هذه القصائد أبياتًا) ولا تحدّده الإعدادات بعد. والبيان الموسوم `10`، الذي كتبه postext 1.24، لا يتلقى إلا تثبيت القواعد 11، كما يتلقاه كل بيان أقدم أيضًا: تُكتب موازنة شبكة المحارف (`pinLegacyGridBalancing`) بصفتها `headings.balancing.enabled: true` في `headings` الساري، حين تضبط الإعدادات المدمجة `cjk.grid.enabled` في النص الأفقي ولا تضبط `enabled` نفسها، لأن 1.24 كان يوازن مثل هذه الصفحة افتراضيًا. وتمنع قواعد 11 أيضًا كسر عنوان الكتاب بعد محرف واحد، وتصفّ الأرقام المحاطة بدائرة كحروف صينية، ونص التصميم CJK بقواعد المتن (#637): البيان المختوم بـ `10` أو أقدم يتلقى `cjk.titleMinChars: 1` (`pinLegacyTitleBreaks`) حين يكون في فصل مقروء عنوان (《 أو 〈 أو `:book[`)، و`cjk.circledNumbers: 'western'` (`pinLegacyCircledNumbers`) حين يكون فيه رقم محاط بدائرة (U+2460–U+24FF، U+2776–U+2793)، و`cjk.composeDesignText: false` (`pinLegacyDesignText`) حين يحوي فصل مقروء أو الإعداد نفسه نصًا CJK؛ كل منها على `cjk` السارية، وفقط إن لم يحدده الإعداد من قبل. وهي تقطع أيضًا الجداول المدرجة وتُخرج `:::columns` في النص الجاري (#634): البيان المختوم بـ `10` أو أقدم يتلقى `tableStyle.splitInline: false` (`pinLegacyInlineTableSplit`) على `tableStyle` السارية حين يضمّن فصل مقروء موردًا، و`layout.flowColumns: false` (`pinLegacyFlowColumns`) على `layout` السارية حين يفتح فصل مقروء سياج `:::columns` في سطر مستقل، كل منهما فقط إن لم يحدده الإعداد من قبل. وهي تتيح كذلك لعنصر عائم رأس عمود الافتتاح الممتد بعرض الصفحة (#639): البيان المختوم بـ `10` أو أقدم يتلقى `layout.floatsUnderOpener: false` (`pinLegacyOpenerHeadFloats`) على `layout` السارية حين يحدّد الإعداد المدمج مستوى عنوان أو نمط عنوان فيه `span: 'page'` ويكون في فصل مقروء عنوان، فقط إن لم يحدده الإعداد من قبل. وقواعد 12 تجعل الصفحة على شبكة المحارف تحمل أسطرها (#717): البيان المختوم بـ `11` أو أقدم، إذا فعّل إعداده المدمج `cjk.grid.enabled`، يتلقى `bodyText.avoidWidows: true`، وفي المنطقة `japan` يتلقى `bodyText.avoidOrphans: true` أيضًا (`pinLegacyGridBreakRules`)، على `bodyText` السارية، كلٌّ منهما فقط إن لم يحدده الإعداد من قبل. وهي تُبقي كذلك حرفين من الحاشية ذات الصفّين على كل جانب من كسر السطر (#720): مثل هذا البيان يتلقى `cjk.warichu.minChars: 1` (`pinLegacyNoteBreaks`) على `cjk` السارية حين يكون في فصل مقروء حاشية `:warichu[` أو يحدّد الإعداد المدمج `citations.notes: 'warichu'`، فقط إن لم يحدده الإعداد من قبل. وفي منطقة اليابان تأخذ الكلمة الغربية المسافة بين الهان واللاتيني بجوار أقواسها وعلاماتها (#721): البيان المختوم بـ `11` أو أقدم، إذا آل إعداده المدمج إلى منطقة اليابان، يتلقى `cjk.latinSpacingEdge: 'letters'` (`pinLegacyLatinSpacingEdge`) على `cjk` السارية حين يكون في فصل مقروء كانا أو كانجي، فقط إن لم يحدده الإعداد من قبل. والمورد الموضوع في النص يأخذ `placement.captionSide` كما يأخذها العنصر العائم بعرض العمود (#729): البيان المختوم بـ `11` أو أقدم، إذا ضبط إعداده المدمج `layout.layoutType: 'oneAndHalf'`، يتلقى `layout.inlineCaptionSide: false` (`pinLegacyInlineCaptionSide`) حين يضمّن فصل مقروء موردًا، فقط إن لم يحدده الإعداد من قبل.

وقواعد 12 تغيّر كذلك طريقة موازنة العمود القصير، والبيان المختوم بـ `11` أو أقدم يحتفظ بما صُفّ به. ترتيب الأدوات (#707): يتلقى `headings.balancing.maxLinesBeforeLoose` بقيمة `maxLinesPerHeading` الخاصة به، أو `4` إن لم يحدّد قيمة (`pinLegacyLooseOrder`)، على `headings` السارية، حين يكون في فصل مقروء عنوان ويكون الإعداد المدمج يوازن أعمدته ويسمح للعنوان بأكثر من سطرين ولا يحدّد المفتاح من قبل. السطر تحت شكل يمتد على عدة أعمدة (#708): يتلقى `headings.balancing.stretchAfterWideFloats: 'always'` (`pinLegacyWideFloatStretch`) على `headings` السارية، حين يُدرج فصل مقروء موردًا أو يشير إليه أو يجعل إطارًا عائمًا، ويكون الإعداد المدمج يوازن أعمدته ولا يوقف `stretchAfterFloats` ولا يحدّد المفتاح من قبل. الفواصل داخل مجموعات الفهرس (#712): يتلقى `index.groups.splitMinEntries: 1` (`pinLegacyIndexGroupSplit`) على `index` السارية، حين يكون في فصل مقروء سياج `:::index` على سطر مستقل ولا يحدّد الإعداد المفتاح من قبل. وحدة القطع المتساوي (#711): يتلقى `headings.balancing.trailingStep: 'grid'` (`pinLegacyTrailingStep`) على `headings` السارية، حين يمكن أن يضع فصل مقروء نصًا خارج تباعد أسطر النص الأساسي (سياج `:::`، سياج شيفرة، اقتباس، قائمة، معادلة معروضة أو استشهاد)، ويكون الإعداد المدمج يوازن أعمدته ويُبقي `trailing` أو `beforeSpan` مفعّلًا ولا يحدّد المفتاح من قبل.

وقواعد 12 تكسر تسمية الإحالة عند مسافاتها حين تكون التسمية كلمات (#702). والبيان المختوم بـ `11` أو أقدم يتلقى `crossRefs.breakLabels: false` (`pinLegacyRefLabelBreaks`) على `crossRefs` السارية، حين يكون في فصل مقروء إحالة (`:ref{…}` أو `\ref{…}` أو `\eqref{…}` أو `@id` من pandoc-crossref) ولا يسمّي الإعداد هذا الخيار: فتبقى كل تسمية الكلمة الواحدة التي صُفّت بها.

وقواعد 12 تشكّل الكلمة الخالية من الحروف (`46.` و`1367)`) بكتابة النص المحيط بها (#725). والبيان الموسوم بـ`11` أو أقدم يأخذ `bodyText.scriptContext: false` (`pinLegacyScriptContext`) على `bodyText` النافذ، أيًّا كان ما في فصوله، إذا لم يسمِّ الإعدادُ هذا الخيار: فتُشكَّل هذه الكلمة وحدها كما خُطِّطت. تعمل القاعدة في الخط الذي يرسم علامات ترقيمه بحسب الكتابة، ولا يُعرف عند قراءة البيان أيَّ الخطوط يحمّل الكتاب.

وقواعد 13 تجعل اللغة التي يُشكَّل بها النص خيارًا (#764)، وقواعد 14 تجعل لغة المستند نفسه قيمته الافتراضية (#770). والبيان الموسوم `13` أو أقدم يتلقى `bodyText.shapingLanguage: 'host'` (`pinLegacyShapingLanguage`) على `bodyText` النافذ، أيًّا كان ما في فصوله، إذا لم يسمِّ الإعدادُ هذا الخيار؛ والبيان الذي يسمّي قيمة يحتفظ بها. فيُقاس نصه بلغة الصفحة التي تستضيف التخطيط، وبلا لغة في العامل، كما خُطِّط. والبيان الموسوم `13` كتبه postext 1.29: فالإصداران 1.29.1 و1.29.2 خطّطا الكتاب الذي لا يسمّي قيمة بلغة المضيف، وهذا ما يفعله التثبيت؛ أما 1.29.0، وكانت قيمته الافتراضية `'document'`، فخطّطه بلغة المستند ولم تتبع فيه Canvas ولا PDF القياس، وهو يُقرأ بـ `'host'` كذلك. والبيان الموسوم `14` الذي لا يسمّي قيمة يُخطَّط بلغة المستند.

وقواعد 13 ترسم كذلك مربع بند المهمة (`- [ ]`، `- [x]`): مربع يرسمه المحرّك، وفيه علامة صح إذا أُنجزت المهمة، ويأخذ حيّزًا خاصًّا به قبل نص البند (#765). والبيان الموسوم `12` أو أقدم يتلقى `unorderedLists.taskBox: 'character'` (`pinLegacyTaskBox`) على `unorderedLists` النافذ، إذا احتوى أحد فصوله بند مهمة ولم يسمِّ الإعدادُ هذا الخيار. فيبقى مربعه الحرف `☐` أو `☑` مصفوفًا بخط القائمة، ويبدأ نص البند بعد عرض ذلك الحرف، كما خُطِّط. وذلك العرض كان يعطيه الخط الذي يحتوي الحرف، وهو في أغلب الكتب خط من خطوط النظام، ولا يُعرف عند قراءة البيان.

```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` هو نص Markdown الذي تُخرجه الإعدادات (سلسلة نصية أو قائمة فصول). من دونه يُثبَّت حجم الصيغ كلما كانت الرياضيات مفعَّلة، والفراغ تحت الحاويات كلما عرّفت الإعدادات نمط فقرة، أما الفراغان وعلامات العناوين والمساحة تحت سطر النقطتين وقطع الإطار وفواصل الشرطات والتقسيم تحت العنوان وفواصل الكلمات المركبة فتُثبَّت دائمًا، إذ لا يستطيع المحرّك أن يعرف هل في الكتاب صيغة، أو حاوية `:::paragraphs`، أو شكل مضمَّن، أو عنوان فيه علامة، أو قائمة تمهّد لها نقطتان، أو إطار، أو شرطة ملتصقة، أو عنوان، أو كلمة مركبة. أما تقسيم الأسطر غير المضبوطة فيُثبَّت بناءً على الإعدادات وحدها، سواء وُجد المحتوى أم لا. رحِّل الإعدادات المخزنة مرة واحدة وخزّنها من جديد تحت `CONFIG_VERSION`: فتثبيت الصيغ يضرب المقياس، والإعدادات التي تُرحَّل مرتين تكبر مرتين. ومن دونه يُثبَّت تخطيط الشعر وأسوار الشيفرة أيضًا، لأن الكتاب قد يحمل قصيدة `:::verse` بلا فاصل أو سورًا، أما الإزاحات المزدوجة فتُثبَّت بحسب الإعدادات وحدها. وتُثبَّت تتمّات أسطر الشعر في 1.23 أيضًا، لأن الكتاب قد يصفّ قصيدة سطرًا سطرًا.

### إخراج حزمة ورسمها

أربع أدوات مساعدة تصل الحزمة المفتوحة بالمحرّك وبالمُخرِجات:

- **`loadBundleFonts(bundle)`** يسجّل أوجه خطوط الحزمة في `document.fonts`، وبايتاتها في سجلّ الخطوط في المحرّك لصور SVG (`registerFontBytes`). انتظر اكتماله (await) قبل الإخراج، لأن الإخراج يقيس النص بالخطوط المتاحة في المتصفح. أما العائلات التي تسمّيها الحزمة ولا تحملها (Google Fonts) فعليك تحميلها بنفسك، كما في أي مستند آخر.
- **`registerBundleImages(bundle)`** يفكّ ترميز الصور لمُخرِج Canvas (`renderPage` و`renderToCanvas`)، ومنها أغلفة مقاطع الفيديو. و**`bundleImageUrl(bundle)`** هو المحلِّل `resourceImageUrl` الذي يحتاجه `renderToHtml`، و**`bundleVideoUrl(bundle)`** محلِّله `resourceVideoUrl` لملفات الفيديو التي تحملها الحزمة. وكلاهما يعيد تلوين أشكال SVG حين يكون `diagramStyle.singleInk` مفعَّلًا، مرة واحدة: يعيدان تلوين الشيفرة ويعلّمان الصور كي لا يلوّنها أي مُخرِج مرة أخرى (انظر [الحبر الواحد على Canvas وفي HTML](https://postext.dev/ar/docs/configuration-resources.md#الحبر-الواحد-على-اللوحة-canvas-وفي-html)). ويضمّن كلاهما أيضًا في كل SVG أوجه الخطوط التي يسمّيها نصه، من خطوط الحزمة نفسها أولًا، ثم من الأوجه المسجّلة في المحرّك (انظر [الخطوط في نصوص SVG](https://postext.dev/ar/docs/configuration-resources.md#الخطوط-في-نصوص-svg))؛ ويُبلّغ `registerBundleImages(bundle, { onWarning })` و`bundleImageUrl(bundle, { onWarning })` عن العائلة التي لا وجه لها.
- **`buildBundle(bundle)`** يُخرج الفصول بالترتيب ويعيد `VDTDocument` لكل فصل. كل فصل يتابع ما قبله: عدّادات العناوين والموارد، والجزء المفتوح، وزوجية الصفحة، وترقيم الصفحات. والفصل الذي يطبع المحتويات (`:::toc`) أو الفهرس الأبجدي (`:::index`) يتلقى مخطط الكتاب كله. ويقبل الخيارات نفسها التي يقبلها `buildDocument`، إضافةً إلى `config` لتجاوز إعدادات الحزمة، و`cache` لمشاركة ذاكرة تخزين مؤقت للقياسات، و`metadata` (انظر أدناه).
- **`bundleResourceBytes(bundle)`** و**`bundleFontProvider(bundle, { decodeWoff2, fallback })`** هما الخياران `resourceBytes` و`fontProvider` للدالة `renderToPdf` في `postext-pdf`. يختار مزوّد الخطوط من الحزمة أقرب وزن للنمط المطلوب. ويحتاج لوجه `.woff2` إلى `decompressWoff2`، وللعائلة التي لا تحملها الحزمة يستدعي `fallback` بمعاملات المُخرِج، بما فيها `request`، ويمرّر ما تعيده كما هو. والبديل الذي لا يجلب إلا ملف `latin` للعائلة يطبع العائلة الصينية التي لا تضمّنها الحزمة مربعاتٍ فارغة؛ أما البديل الذي يجيب بشرائح، مثل `sliceFontProvider` في [خطوط الصينية واليابانية والكورية](https://postext.dev/ar/docs/configuration-pdf-output.md#خطوط-الصينية-واليابانية-والكورية)، فيطبعها كاملة.

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

لإخراج فصل واحد بنفسك، مرّر `bundle.chapters[i].markdown` و`bundle.resources` و`bundle.config` إلى `buildDocument`، كما تفعل مع أي مستند.

**البيانات الوصفية للكتاب.** كما في Sandbox، البيانات التمهيدية (front matter) للفصل الأول هي بيانات الكتاب: يسلّم `buildBundle` قيم `title` و`author` وسائرها إلى كل فصل، فتصحّ الترويسات `{title}` و`{author}` على كل صفحة، ويحملها `doc.metadata` في كل فصل. وتُتجاهَل كتلة البيانات التمهيدية في أعلى فصل لاحق: يُتعرَّف عليها بسطرَي `---` وتُمحى دون تحليلها، فلا يضرّ YAML قد يرفضه المحلِّل. ويوفّر `options.metadata` القيم التي لا تضبطها البيانات التمهيدية (والغلبة للبيانات التمهيدية). ويصل عدد صفحات الكتاب إلى كل فصل أيضًا: يطبعه `{bookTotalPages}`، بينما يعدّ `{totalPages}` صفحات الفصل (انظر [عدد صفحات الكتاب](https://postext.dev/ar/docs/configuration-headers-footers.md#عدد-صفحات-الكتاب)).

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

**موضع بداية الكتاب.** قد تضمّ الحزمة جزءًا من مطبوعة أطول: الصفحات من 58 إلى 61 من عدد، أو الفصل الرابع من كتاب مدرسي. يحدّد `start` ما يسبقها، بحقول `continuation` في `buildDocument`: عدد الصفحات قبل الصفحة الأولى (`pageIndexOffset`، وهو يحدّد الجهة التي تقع فيها الصفحة 1، ومعها الهوامش المتناظرة وترويسات الصفحات الفردية والزوجية)، وترقيم الصفحات الساري (`pageNumbering`)، وعدّادات العناوين (`headings`)، والجزء المفتوح (`part`)، وعدّادات الموارد والعبارات المرقّمة والحواشي السفلية والأسطر. تكتبه `createBundle` في `preset.json` باسم `start`، وتعيده `openBundle` في `bundle.start`، وتُخرج `buildBundle` به الفصل الأول كما تفعل `buildDocument({ markdown, continuation: start }, config)`، وتصل به الفصول التالية؛ ويحسب `{bookTotalPages}` الصفحات السابقة للكتاب أيضًا. البيان الذي لا يحوي `start` يُقرأ كما كان، والقارئ الذي لا يعرف الحقل يتجاهله. و`bookPageCount` ليس منه: القارئ هو من يعدّ الصفحات. وفي حزمة بعدة لغات يمنح `localized[…].start` إحدى الطبعات بداية خاصة بها.

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

### مثال حي: فتح حزمة

يحمّل هذا المثال على CodePen كتابًا نموذجيًا من فصلين (`lantern.postext`، بخطه الخاص وشكل SVG وجدول) من المستودع. ويسجّل خطوط الحزمة وصورها، ويُخرج الكتاب بـ`buildBundle` ويرسم كل صفحة. ويُنتج زر *Make the PDF* المستندات نفسها بـ`postext-pdf`، مضمِّنًا خطوط الحزمة. اختر ملف `.postext` خاصًا بك، مصدَّرًا من Sandbox مثلًا، لتراه بالطريقة نفسها.

> **مثال قابل للتشغيل: Postext · فتح حزمة .postext** — افتح ملف .postext بمكتبة postext، وأخرج الكتاب وارسمه على Canvas وفي PDF. ([الشيفرة](https://github.com/drnachio/postext/tree/main/docs/examples/open-bundle))

### إنشاء حزمة

يكتب `createBundle` ملف `.postext` من مستند: فصوله وإعداداته وموارده والملفات التي تشير إليها.

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

| المُدخل | المعنى |
| --- | --- |
| `name`، `id`، `description`، `locale` | البيانات الوصفية للبيان. القيمة الافتراضية لـ`id` صيغة مختصرة (slug) من `name`. |
| `chapters` أو `markdown` | الكتاب، `{ title?, markdown }` لكل فصل، أو مستند واحد. |
| `config` | إعدادات `PostextConfig`. القيم المساوية للقيم الافتراضية لا تُكتب في البيان. |
| `resources` | الموارد. تسمّي الصورة ملفها بـ`bitmap.fileId` / `svg.fileId` (و`svg.pdfFileId` لنسخة الطباعة الأصلية). |
| `files` | الملفات حسب `fileId` (كائن أو `Map`): الصور التي تشير إليها الموارد، وملفات الخطوط التي تشير إليها متغيرات `config.customFonts`. يمكن أن تكون القيم `Uint8Array` أو `ArrayBuffer` أو `Blob` أو سلسلة نصية (شيفرة SVG). |
| `thumbnail` | `{ data, mime }`: صورة غلاف (PNG أو JPEG أو WebP أو GIF أو SVG). |
| `canvasScope` | القيمة `'book'` تطلب من العارضين إخراج الكتاب كله لوحةً واحدة. |
| `start` | موضع بداية الكتاب، في حزمة تضمّ جزءًا من كتاب أطول: هو `continuation` الذي يُبنى به مستند مفرد. يُكتب في البيان باسم `start`. |
| `mtime` | تاريخ التعديل المكتوب على كل ملف في الأرشيف (`Date` أو طابع زمني أو سلسلة تاريخ). إذا أُغفل فهو وقت الاستدعاء، فيعطي استدعاءان بالمُدخل نفسه بايتات مختلفة. مرّر تاريخًا ثابتًا يعطِ المُدخل نفسه البايتات نفسها، فتستطيع حساب بصمتها (hash) أو مقارنتها. يحفظ ملف zip تاريخًا ووقتًا بلا منطقة زمنية، بخطوات من ثانيتين، من 1980 إلى 2099، ويُكتب التاريخ بالتوقيت المحلي للجهاز. وللحصول على بايتات متطابقة على كل جهاز، ابنِ التاريخ من حقول محلية، مثل `new Date(1980, 0, 1)`: فالطابع الزمني أو السلسلة المنتهية بـ`Z` يسمّيان لحظة بعينها، تقع في وقت محلي مختلف في كل منطقة زمنية (`'1980-01-01T00:00:00Z'` ما زال في 1979 غربَ UTC). والتاريخ الواقع خارج تلك السنوات، بالتوقيت المحلي، يرمي خطأً. |
| `localized` | لغات أخرى للكتاب نفسه، حسب وسم اللغة: `{ es: { name?, description?, chapters?, config?, resources? } }`. تصبح المُدخلات أعلاه حينئذٍ محتوى `locale`، وهو مطلوب. انظر [الحزم الثنائية اللغة](https://postext.dev/ar/docs/configuration-bundles.md#الحزم-الثنائية-اللغة). |

تعيد الدالة بايتات الأرشيف `bytes`، و`manifest` المكتوب على شكل `preset.json`، وكل ملف في `files` (المسار ← البايتات)، وقائمة `warnings`. تُسمّى الملفات باسم معرّف المورد (`resources/lantern.svg`) أو اسم ملف الخط (`fonts/…`)، والفصول بترتيبها وعنوانها (`chapters/01-dusk.md`). تُعرَّف الخطوط في `fonts` في البيان، ولا تُعرَّف أبدًا داخل `config.customFonts`. وتُستبعد بعض الأشياء، مع تحذير لكل منها:
- مورد أو وجه خط ليس ملفه في `files`
- وجه `.woff` (لا يستطيع مُخرِج PDF تضمينه)
- عائلة معلَّمة بـ`redistributable: false`

في المتصفح، سلّم `bytes` إلى رابط تنزيل: `URL.createObjectURL(new Blob([bytes], { type: 'application/zip' }))`. وفي Node، اكتبها بـ`fs.writeFile`. لا يحتاج `createBundle` ولا `openBundle` إلى DOM. تستخدم ملفات التوزيع (dists) مسارات وحدات بلا امتداد، لذا تحتاج تحت Node المجرّد، دون أداة تجميع (bundler)، إلى خطاف حلّ (resolve hook). ويعرض الملف `docs/examples/open-bundle/build-sample.mjs` في المستودع خطافًا كهذا في بضعة أسطر.

### الحزم الثنائية اللغة

يمكن لملف `.postext` أن يحمل كتابًا بعدة لغات، ويقرؤه `openBundle(bytes, { locale })` بأيٍّ منها. ويكتب `createBundle` ملفًا كهذا من `localized`: مُدخل واحد لكل لغة إضافية، فيه ما يختلف عن المحتوى الأساسي (مُدخل `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' });   // Spanish chapters, config and captions
```

- **`chapters`**: الكتاب بتلك اللغة. تذهب ملفات الفصول إلى مجلد لكل لغة (`chapters/en/01-dusk.md`، `chapters/es/01-anochecer.md`) ويصبح `chapters` في البيان خريطة من اللغة إلى الفصول. اللغة التي لا `chapters` لها تقرأ الفصول الأساسية؛ وإذا لم يكن لأي لغة فصول خاصة بها، تبقى قائمة واحدة.
- **`config`**: الإعدادات لتلك اللغة. يحلّ كل مفتاح من المستوى الأعلى محل المفتاح المشترك برمّته حين تُقرأ الحزمة بتلك اللغة، فـ`headings` أعلاه يحلّ محل كائن `headings` كله. والمفاتيح المُغفلة، أو المساوية للمشتركة، مشتركة ولا تُكتب، فتمرير إعدادات اللغة كاملة يصلح كما يصلح تمرير المفاتيح القليلة التي تتغير. والمفتاح المضبوط على قيمه الافتراضية بينما المشترك ليس كذلك (`layout: {}`) يُكتب كما هو، فيعيد ضبط القيمة المشتركة. الخطوط مشتركة: تنضم عائلات `customFonts` الخاصة بلغة ما إلى `fonts` في الحزمة.
- **`resources`**: صياغة الموارد المشتركة، مطابَقةً بـ`id`: `caption` و`note` و`altText` و`table` في الجدول. والصورة التي فيها كلمات يمكن أن يكون لها عملها الفني الخاص: يسمّي `bitmap.fileId` أو `svg.fileId` (و`svg.pdfFileId`) ملفًا آخر في `files`، يُكتب على شكل `resources/es/lantern.svg`. أما الحقول الأخرى، كالنوع أو الموضع، فمشتركة. والمعرّف غير الموجود بين `resources` يُستبعد مع تحذير، والصورة المفقودة للغةٍ ما تُبقي تلك اللغة على الصورة المشتركة، مع تحذير أيضًا.
- **`name`**، **`description`**: عنوان الكتاب ووصفه بتلك اللغة، ويُكتبان في `localized[…].name` و`description` حين يختلفان عن المشتركَين. يعيدهما `openBundle` بتلك اللغة في `bundle.name` و`bundle.description`، ويعرض Sandbox الطبعة بهذا العنوان عند الاستيراد وفي مكتبته، ويطبعهما `postext info` و`postext check`. ويمكن لمُدخَل في `index.json` لمصدر قوالب أن ينسخهما في `localized` (`localized.ca.name`) لتعرض المكتبة كل طبعة بعنوانها قبل فتحها. واللغة التي لا تذكرهما تحتفظ بـ`name` و`description` الواردين في البيان.

يسرد البيان كل لغة في `locales` (`['en', 'es']`)، ويحتفظ باللغة الأساسية في `locale` ويخزّن البقية تحت `localized`. ويقرأ `openBundle` دون لغةٍ اللغةَ الأساسية.

**أي لغة يحصل عليها القارئ.** يقدّم `openBundle(bytes, { locale })` اللغة المطابقة تمامًا، وإلا فاللغة دون متغيرها الإقليمي (`es-MX` يقرأ `es`)، وإلا فاللغة الأساسية، ويبيّن `bundle.locale` أيّها قدّم. تأتي الفصول والصياغة دائمًا من اللغة نفسها. وتحتفظ اللغة الأساسية بالصياغة المشتركة حتى إن حمل `localized` متغيرًا إقليميًا منها: فالحزمة بـ`pt-PT` التي فيها مُدخل `pt-BR` تقرأ التعليقات البرازيلية لـ`pt-BR` وحدها، والتعليقات المشتركة لـ`pt-PT` و`pt`.

### مثال حي: إنشاء حزمة

يبني هذا المثال على CodePen كتابًا من فصلين فيه شكل SVG، ويسرد الملفات التي كتبها `createBundle` مع البيان. ويعرض الأرشيف للتنزيل، ثم يفتحه من جديد بـ`openBundle` ويرسم صفحته الأولى: الدورة الكاملة ذهابًا وإيابًا في بضعة أسطر. استورد الملف المنزَّل في Sandbox لتواصل العمل عليه هناك.

> **مثال قابل للتشغيل: Postext · إنشاء حزمة .postext** — اكتب ملف .postext بالدالة createBundle من postext، ونزّله وافتحه من جديد. ([الشيفرة](https://github.com/drnachio/postext/tree/main/docs/examples/create-bundle))

### العمل بالحزم

لأن Sandbox ومهارة الوكيل ومكتبة `postext` تقرأ كلها الملف نفسه وتكتبه، فملف `.postext` وسيلة عملية لتسليم كتاب من أداة إلى أخرى:

- **ابدأ من حزمة.** انقل منشورًا قائمًا بـ[مهارة الوكيل](https://postext.dev/ar/docs/skill.md)، أو صمّم كتابًا في [Sandbox](https://postext.dev/ar/sandbox.md) وصدّره (**تنزيل (.postext)** في قائمة ⋯ في صفّه ضمن لوحة الكتب). حمّل الملف من برنامجك بـ`openBundle` لترسمه على Canvas أو في HTML أو PDF. واحتفظ بالملف مصدرًا للكتاب: عدّل الفصول أو الإعدادات أو الموارد في الشيفرة واكتبه من جديد بـ`createBundle`، أو أعد تحميله ببساطة كلما تغيّر.
- **صحّح الأخطاء واضبط التفاصيل في Sandbox.** حين يحتاج شيء في مخرجات برنامجك إلى عمل (شكل يقع في الصفحة الخطأ، أو نمط عنوان، أو توازن الأعمدة)، صدّر ما يُخرجه برنامجك بـ`createBundle`. استورد ذلك الملف في Sandbox (*الكتب › جديد › افتح ملف .postext…*)، وأصلح النص أو التصميم أو الأشكال بالمعاينة الحية ولوحة الفحوص وعرض PDF، ثم صدّره من جديد. بعدها يحمّل برنامجك الملف المصحَّح بـ`openBundle`. أو انسخ ما تغيّر إلى شيفرتك: لا يحتوي `config` في البيان إلا على القيم التي تختلف عن الافتراضية، فيُقرأ كفرقٍ (diff) قصير.

### الواجهة البرمجية منخفضة المستوى

يصدّر `postext/bundle` أيضًا اللبنات التي يقوم عليها `openBundle` و`createBundle`، للمضيفين الذين يخزّنون الحزم أو يقدّمونها بطريقتهم (مجلد غير مضغوط عبر HTTP، أو سجلات في قاعدة بيانات):

- **`openBundleZip(bytes)` / `zipBundle(files, { mtime })`**: طبقة الأرشيف. يتسامح الفتح مع مجلد في المستوى الأعلى، ويتجاهل مُدخلات `__MACOSX` والملفات التي يبدأ اسمها بنقطة (dotfiles). وتُرفض المسارات التي تخرج من الحزمة. ويؤرّخ `mtime` الملفات كما يفعل مُدخل `createBundle`.
- **`readBundle(manifest, readFile, options)`** يقرأ بيانًا مع دالة الاستدعاء `readFile(path)` ويحوّلهما إلى فصول وإعدادات وموارد وصور وخطوط. يضبط `options` اللغة، وطريقة تسمية معرّفات الملفات (`ids`)، والإعدادات الأساسية (`baseConfig`، تحت إعدادات البيان؛ وهي افتراضيًا لوحة الألوان وأنواع الموارد في `bundleBaseConfig` بلغة الحزمة، التي يعيدها `resolveBundleConfigLocale(manifest, locale)`، وعلى المضيف الذي يمرّر `baseConfig` خاصًا به أن يكيّفه مع تلك اللغة؛ وتحت بيان أقدم من `configVersion: 4` يُثبَّت `math` الخاص بها مع ما في الحزمة، وتحت بيان أقدم من 5 الفراغ تحت المورد المضمَّن في `layout` الخاص بها، وتحت بيان أقدم من 6 الفراغ في الأطر في `layout`، والمساحة تحت سطر النقطتين في `bodyText`، والعلامات المضمَّنة في `headings`، وأحجام الحروف الاستهلالية، وتحت بيان أقدم من 7 فواصل الشرطات وتقسيم الأسطر غير المضبوطة في `bodyText`، وتحت بيان أقدم من 8 التقسيم تحت العنوان في `headings`، وفواصل الكلمات المركبة والفراغ تحت حاويات `:::paragraphs` في `bodyText`، انظر [الحزم التي كتبها postext 1.4 أو ما قبله](https://postext.dev/ar/docs/configuration-bundles.md#الحزم-التي-كتبها-postext-14-أو-ما-قبله))، وطريقة قياس المقاسات الأصلية. ويقرأ `readResolution` دقة كل صورة نقطية من ملفها إلى `bitmap.fileResolution`؛ وقيمته الافتراضية true حين تضبط الحزمة `layout.bitmapResolution: 'file'`.
- **`planBundle(meta, content)` / `resolveBundleFiles(plan, sources)`**: جانب الكتابة، مقسومًا إلى خطة خالصة (أسماء الملفات والبيان) وجلب البايتات عبر دالتَي الاستدعاء `readBlob` / `readFont`.
- **`isBundleManifest(value)`**، ومنتقيات اللغة (`pickChapterSpecs`، `pickLocaleOverrides`، `pickBundleView`، `resolveBundleLocale`، `resolveBundleConfigLocale`)، و`svgSize` / `bitmapSize` / `bitmapInfo` (بكسلات الصورة النقطية والدقة التي يذكرها ملفها)، وأنواع الصيغة (`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)` و`LEGACY_MATH_SIZE`** (0.5 ÷ 0.442): تحوّل إعدادات مخزنة إلى مصطلحات اليوم (انظر [الحزم التي كتبها postext 1.4 أو ما قبله](https://postext.dev/ar/docs/configuration-bundles.md#الحزم-التي-كتبها-postext-14-أو-ما-قبله)). يطبّقها `readBundle`؛ ويستطيع ذلك أيضًا المضيف الذي يخزّن الإعدادات بطريقته، مرة واحدة لكل نسخة مخزنة.

يقوم Sandbox على هذه الأدوات، ويضيف إليها معرّفات التخزين الخاصة به وسجلات الصفحات في `layouts.json`.
