الفصل 2.1 · الجزء I · الأسس
البنية: خط الإخراج
تمريرة التوزيع وجولاتها: كيف يضع المحرّك الأسطر والعناصر العائمة والحواشي والأعمدة، ويعيد التمريرة حتى يستقر الإخراج
باختصار
هذه الصفحة للمبرمجين الذين يريدون أن يعرفوا كيف يضع Postext الأشياء على الصفحة. يمرّ Postext على النص من أوله إلى آخره، ويضع كل سطر وصورة وجدول وحاشية وإطار في مكانه. وإذا غيّرت خطوةٌ لاحقة شيئًا سابقًا، كرقم صفحة في إحالة، بدأ التمريرة من جديد. ويتوقف حين لا يتحرك شيء بعد ذلك. وتسرد الصفحة كل خطوة، وما تقرّره، وعدد المرات التي يمكن أن تعمل فيها.
#خط الإخراج
تمريرة توزيع واحدة، تُشغَّل بقدر ما يحتاج الإخراج. هذا هو خط الإخراج كله.
تمرّ التمريرة (placeDocumentPass، في packages/postext/src/pipeline/build.ts) على المحتوى من أول كتلة إلى آخرها. وتفعل لكل كتلة كل ما يلزم لتنضيدها: تقيسها بعرض العمود الذي توشك أن تدخله، وتنظر هل تتسع، وتقسمها أو تنقلها إلى ما بعده وفق قواعد الكسر والإبقاء معًا، وتعرض خانة على العناصر العائمة التي تحيل إليها الكتلة، وتعيد التدفق إلى شبكة خطوط الأساس (راجع نظام الإيقاع العمودي). وليس للعناصر العائمة ولا للأرامل ولا للشبكة تمريرة خاصة: تُحسم كلها حين تستقر كل كتلة.
بعض القرارات لا يمكن اتخاذها داخل مرور واحد، لأنها تتوقف على ما ينتهي إليه المرور: أرقام الصفحات التي يطبعها جدول المحتويات، والارتفاع الذي يقطع عنده إطار ممتد على عرض الصفحة الأعمدةَ التي فوقه، والأسطر التي ما زال يحتاج إليها عمود قصير. لهذه القرارات يعيد buildDocument تشغيل التمريرة مع تلميحات (hints) مأخوذة من نتيجة التمريرة السابقة. تتبع المراحل الخمس أدناه الكتلة خلال التمريرة؛ ويتناول مشغّل التمريرة وموازنة الأعمدة إعادات التشغيل.
#المرحلة 1: هيكلة المحتوى
- المُدخل:
PostextContentخام - العمل: تحليل markdown إلى كتل محتوى، وحلّ إحالات الموارد مقابل
resources[]بالمعرّف، وإخراج تعريفات الحواشي السفلية ([^id]:) من التدفق وترقيم علاماتها بترتيب الإحالة، وترقيم العناوين، وتخطيط العناصر العائمة: أي الموارد يُعوَّم، وبعد أي كتلة (notes[]لا تُقرأ) - المُخرج: قائمة مسطّحة من كتل المحتوى (مصنّفة وممتلئة بالمحتوى، لكن بلا صفحة ولا عمود مخصّصين) وخطة العناصر العائمة
- تجري: في بداية كل تمريرة توزيع. يُقرأ التحليل من نتيجته المحفوظة بعد التمريرة الأولى؛ والباقي يُحسب من جديد
#المرحلة 2: قياس النص
- المُدخل: كتلة محتوى فيها نص، وعرض العمود الذي توشك أن تدخله
- العمل: قياس أسطر الكتلة بذلك العرض عبر وحدة القياس المخصّصة (
packages/postext/src/measure/)، التي تبني تقسيم الكلمات بالواصلة وضبط الأسطر والمقاطع السطرية المنسّقة وكسر الأسطر بخوارزمية Knuth-Plass فوق Pretext. والنتيجةVDTLine[]الخاصة بالكتلة وارتفاعها الكلي - تفصيل مهم: القياس مخزّن مؤقتًا. تبني
cachedMeasureBlock/cachedMeasureRichBlock(فيmeasure/cache.ts) مفتاحها من النص والخطوط والعرض وكل خيار يؤثر في الإخراج، فإعادة قياس فقرة لم تتغير مجرد بحث في خريطة - المُخرج: أسطر الكتلة، بأبعاد دقيقة بالبكسل
- تجري: حين يبلغ المرور الكتلة، لا قبل ذلك، لأن العرض يتوقف على الموضع الذي تستقر فيه الكتلة: عمود بجوار عمود جانبي، أو الأسطر المجاورة لشكل يلتف حوله النص، أو الأعمدة الفرعية لمجموعة
:::columns. والكتلة التي تنتقل إلى عمود بعرض آخر تُقاس من جديد بذلك العرض
تنقسم الوحدة بوضوح حسب المسؤولية: plain.ts يقيس المقاطع البسيطة، وrich.ts يقيس المقاطع المختلطة من غامق ومائل ورياضيات، وfont.ts يبني سلاسل الخطوط ويدير دورة حياة الذاكرة المؤقتة، وcanvas.ts يغلّف الدوال الأولية لقياس عرض النص على canvas. وتفصيل واحد في دورة الحياة مهم عمليًا: يجب ألا تبقى عروض الحروف المقيسة بخط احتياطي بعد وصول الخط الحقيقي. فـcanvas.ts يحتفظ بعروضه لكل سلسلة خط وبجيل للقياس؛ وevictFontFamilies (التي تُستدعى حين يكتشف بناءٌ أن أوجه عائلة تغيّرت، ومن watchFonts وprepareFonts وloadBundleFonts) تطرح عروض تلك العائلات وتمسح ذاكرة Pretext المؤقتة، وMeasurementCache التي مُلئت في جيل أقدم تطرح كتلها المنضّدة بتلك العائلات عند أول بحث تالٍ فيها. وتفعل clearMeasurementCache() الشيء نفسه لكل العائلات. أما الأوجه نفسها فيحمّلها قبل الإخراج fonts/prepare.ts (prepareFonts)، ويقرأ fonts/faces.ts الأوجه التي استعملها مستند بعد إخراجه، من أجل تحذيرات fontFallback.
تسلك الفقرات الصينية واليابانية والكورية طريقًا آخر داخل الوحدة نفسها. الفقرة التي تفوق فيها حروف CJK عدد المسافات بين الكلمات تذهب إلى مُنضِّد CJK (cjkCompose.ts). يقطع النص إلى وحدات (حرف، أو مقطع من نص لاتيني، أو شرطة بعرض em مزدوج أو علامة حذف، أو صندوق ذرّي كالشارة أو علامة الحاشية)، ويقيس كل وحدة مرة واحدة، ويملأ الأسطر بطريقة أول ملاءمة (first-fit) وفق قواعد بداية السطر ونهايته في cjkClasses.ts: يستوعب السطر العلامةَ التي لا يجوز أن تفتتح السطر التالي بالتخلي عن الفراغ المحيط بعلامات الترقيم (cjkPunctuation.ts) قبل أن ينقل حرفًا إلى السطر التالي. ثم يُوزَّع السطر المضبوط بين حروفه. والمخرجات أسطر VDTLine عادية تحمل مقاطعها ما تحتاج إليه المُخرِجات لرسمها كما قيست بالضبط: tracking (بكسلات بعد كل حرف، داخلة أصلًا في عرض المقطع)، وinkOffset للعلامة التي تخلّت عن فراغها، وhangs، والمسافات بين حروف الهان والحروف اللاتينية بوصفها مقاطع autospace، والعلامات وقراءات الروبي (ruby) وصفوف الواريتشو (warichu) في التعليقات التوضيحية الصينية. وفي المستند الياباني يتبع المُنضِّد نفسه JLReq: تُقرأ فئات الكينسوكو في المستويات اليابانية من المحارف الواقعة على طرفَي كل وحدة لحظة الكسر، ويُضغط بياض علامات الترقيم ويُعاد بترتيب JLReq، وتُخطَّط الفوريغانا في rubyJis.ts (توزيع بنسبة 1:2:1، وامتداد فوق الكانا، وكلمات الجوكوغو)، وتوسّع علامات الكانبون وحدةَ المحرف الذي تحمله. والكلفة خطية في طول الفقرة: الفصل الأول من 紅樓夢 (6,949 حرفًا) يُنضَّد بقياس 1,298 حرفًا، كل حرف مميز مرة واحدة، في حين كان قياس بادئات الفقرة يتطلب 636,948. راجع الإخراج الصيني.
وتحت ذلك كله، هنا تثبت Pretext جدواها. استدعاء prepare() هو الجزء المكلف: يحلّل النص باستعمال محرّك الخطوط في canvas ويخزّن النتيجة. أما استدعاء layout()؟ حساب محض، شبه مجاني. وهذا الفصل بين الاستدعاءين هو جوهر الأمر. فبعد تحضير النص، يستطيع المحرّك أن يكسره من جديد بكلفة لا تُذكر: بعرض آخر، لفقرة تكمل في عمود أضيق، أو بسطر زائد، لفقرة ترخيها موازنة الأعمدة. حضّر مرة واحدة، وأخرج بقدر ما تحتاج.
// Simplified: how the measure module uses pretext internally
const prepared = prepare(paragraphText, '16px/1.5 Inter');
const { height } = layout(prepared, columnWidth, 24); // 24px line-height
// => "This paragraph is 168px tall at 320px column width — that's 7 lines."#المرحلة 3: التوزيع على الصفحات والأعمدة
- المُدخل: كتلة مقيسة
- العمل: وضع الكتلة في العمود الحالي. إنشاء عقد
VDTPageوVDTColumnكلما احتاج إليها التدفق. تتبّعavailableHeightلكل عمود. وحين لا تتسع كتلة، قسمتها (المرحلة 5) أو الانتقال إلى العمود أو الصفحة التالية - الاستراتيجية: وضع جشع بأول ملاءمة (greedy first-fit). تتبع فواصل الأعمدة والصفحات أبسط توزيع صالح
- المُخرج: تحصل الكتلة على
pageIndexوcolumnIndexوbbox
هذه هي اللحظة التي تصير فيها VDT مستندًا حقيقيًا. قبل هذه المرحلة، الكتلة مدخل في قائمة مسطّحة، وليس لها عنوان. يخصّص التوزيع كل واحدة لصفحة وعمود، كمن يصب الماء في شبكة من الأوعية: املأ العمود 1 حتى يفيض، وانتقل إلى العمود 2، وحين تمتلئ الصفحة ابدأ صفحة جديدة.
قبل أن تستقر أي كتلة محتوى في صفحة، تحجز التمريرة مساحة للعناصر البنيوية: الترويسات والتذييلات الجارية (تُخرَج كخانات تصميم من config.header / config.footer) وأي أشرطة عائمة معلّقة للصفحة المفتوحة حديثًا. هذه الحجوزات تُنقص availableHeight لكل عمود، فحين تبدأ كتل المحتوى بالتدفق يعرف المحرّك بالضبط كم من المساحة متاح.
أشرطة الأعمدة والأعمدة الممتدة. page.columns مصفوفة مسطّحة بترتيب القراءة، لكن الصفحة ليست دائمًا صفًا واحدًا من الأعمدة. في تخطيط متعدد الأعمدة، الكتلة الواقعة في التدفق والممتدة على عرض الصفحة تقطع الصفحة إلى أشرطة (bands) متراصّة. وتُعدّ من هذا النوع أربعة أشياء: :::callout يحدّد نمطه أو سياجه span: 'page'، ومجموعة :::columns{span="page"} في النص الجاري، و::resource مضمّن في التدفق يُحلّ وضعه إلى { position: 'here', span: 'page' }، وشريط مصوّر يُنضَّد على عرض الصفحة حيث كُتب (:::strip{span=page}). تُغلَّف الثلاثة الأخيرة قبل الوضع في إطار بلا حدود ممتد على عرض الصفحة (pipeline/flowColumns.ts، pipeline/spanEmbeds.ts)، فتنضّد آلية واحدة الأربعة كلها. عند القطع، تُغلق الأعمدة النصية للشريط الحالي عند خط القطع (يُقيَّد ارتفاعها، وتصير availableHeight صفرًا)، وتحصل الكتلة على عمود خاص بها بعرض كامل مع kind: 'span'، ويُلحق تحتها شريط جديد من الأعمدة النصية (band + 1، بالموضع x والعرض نفسيهما، وبالحافة السفلى نفسها للشريط الذي يحلّ محلّه). لا تُضاف الأعمدة إلا إلحاقًا، فيبقى columnIndex يشير إلى page.columns[i]، ولا تحتاج المُخرِجات إلى رسم خاص: كل عمود يُقصّ إلى مربعه المحيط (موسّعًا بـ columnClipRect: 2pt لحبر الحروف، يُضاف إليها ما تتدلى به عناصر تصميم العمود خارج جانبيه، كلسان العنوان أو شارة الإطار، وما يبلغه تصميم العنوان فوق أعلاه؛ أما أسفله فيبقى الحافة، وهو المستطيل نفسه في واجهتي canvas وPDF)، ويُرسم الخط الفاصل بين الأعمدة لكل شريط بين الأعمدة النصية المتجاورة، بدءًا من أسفل الشريط الذي يشغله عنوان ممتد على عرض الصفحة في رأسها (columnRuleSegments)، وبخط الصفحة نفسها حين يحدّده قسم منسّق (VDTPage.columnRule، ويُقرأ عبر pageColumnRule). تتجاهل موازنة الأعمدة الأعمدةَ الممتدة والأعمدةَ الجانبية والأشرطةَ ذات الارتفاع الصفري. تقطع الكتلة الممتدة مباشرةً حيث يكون الشريط مستويًا (أعلى الصفحة، أو مباشرة بعد عنوان افتتاحي، أو كتلة ممتدة أخرى، أو شريط عائم علوي)؛ أما إذا وصلت إلى شريط غير مستوٍ فتقترح بدلًا من ذلك سقفًا للشريط (band cap) (packages/postext/src/pipeline/bandCaps.ts): تُقصَّر أعمدة الشريط الذي يبدأ بكتلة محتوى معيّنة إلى ceil(Σ used / N / grid) سطرًا، ويعيد buildDocument تشغيل تمريرة التوزيع مع السقف (ويزيده سطرًا في كل مرة حين يفيض الشريط المسقوف، حتى MAX_BAND_CAP_RETRIES = 3 مرات، ثم يتخلى عنه، فترجع الكتلة إلى أعلى الصفحة التالية)، فيملأ النص الأعمدة المقصّرة وفق كل قواعد الوضع، وينتهي مستويًا عند القطع، وتحتفظ الأعمدة المغلقة بما يتبقى من فراغ في availableHeight لتستوعبه الموازنة. والآلية نفسها تسوّي الشريط الختامي للفصل وللمستند (headings.balancing.trailing): حين يُبلغ افتتاح فصل، أو :::part، أو إطار placement: 'fixed' يختم الفصل، أو نهاية المستند، وأعمدة الشريط الحالي غير مستوية، يُقترح سقف من نوع kind: 'trailing' مفتاحه كتلة الحدّ؛ ولأن مفتاح السقف هو الكتلة التي تفتتح شريطه (وهذه الكتلة تتحرك كلما استوعبت صفحة سابقة أسطر موازنة إضافية)، تُحلّ السقوف الختامية بعد استقرار موازنة الأعمدة، مع تجميد تلميحات الموازنة، ثم تتيح جولة تحسين قصيرة لأدوات الموازنة (levers) أن تملأ ما تركه القطع ناقصًا. أما الإطارات ذات placement: 'fixed' فتغادر التدفق: يُثبَّت الإطار إلى منطقة محتوى الصفحة / مربع القص (trim box) / مربع النزف (bleed box)، وتتخلى الأعمدة النصية التي يغطيها عن تلك المنطقة (تُقطع من الأسفل أو الأعلى كشريط عائم، وتنتقل إلى الصفحة التالية عند التعارض)، ويذهب الإطار مع أبنائه إلى page.floats.
الصفحات العمودية. مع layout.writingMode: 'vertical-rl' تُخرج التمريرة صفحة أفقية مُدارة ربع دورة باتجاه عقارب الساعة. في مثل هذه الصفحة تكون منطقة المحتوى والأعمدة والكتل والأسطر والعناصر العائمة ومناطق الحواشي في إحداثيات التدفق (flow coordinates)، ويحمل VDTPage.flow الدوران الذي ينقلها إلى الورقة: نقطة التدفق (x, y) تقع عند (عرض الصفحة − y, x). ولا شيء في المراحل اللاحقة يحتاج إلى معرفة ذلك: الكسر والعناصر العائمة وقواعد الإبقاء معًا والموازنة تعمل في إطار التدفق كما في أي صفحة، وعمود التدفق صفٌّ (tier) على الورقة، ويطبّق كل مُخرِج الدوران حين يرسم (flowToPage وpageToFlow يحوّلان النقاط في الاتجاهين). وتبقى الترويسات وأرقام الصفحات وعلامات القص والخلفية في إحداثيات الورقة. تُخرَج الأشكال والجداول كتلًا قائمة تُدار عائدةً داخل الإطار، والحروف التي تقف قائمة تُدار عائدةً واحدًا واحدًا وقت الرسم.
#المرحلة 4: وضع الموارد
- المُدخل: خطة العناصر العائمة من المرحلة 1، والصفحات كما وُضعت حتى الآن
- العمل: تعويم كل مورد مُحال إليه إلى أول خانة حرّة بعد أول إحالة إليه: أسفل العمود المُحيل، أو أعلى / أسفل العمود الفارغ التالي، أو شريط في الصفحة التالية (
packages/postext/src/pipeline/floatPlacement.tsيخطّط العناصر العائمة، وpipeline/floatSlots.tsيعدّد الخانات ويقيسها؛ وتحجز تمريرة التوزيع الأشرطة في أثناء مرورها) - تجري: فور استقرار الكتلة التي تحمل أول إحالة، داخل المرور نفسه؛ والنص الذي يوضع بعد ذلك يتدفق حول الشريط
- حلّ الوضع: لكل مورد، يحلّ المحرّك
resource.placement، ثمresourceType.defaultPlacementالخاص بالنوع، ثم القيمة الافتراضية المدمجة{ position: 'auto', span: 'column' }
| حقل الوضع | السلوك |
|---|---|
position: 'auto' | يأخذ المورد أول خانة حرّة بعد إحالته، في الأعلى أو الأسفل؛ وهذه هي القيمة الافتراضية |
position: 'top' | الخانات العلوية فقط: شريط أعلى العمود الفارغ التالي أو الصفحة التالية، يدفع محتوى العمود تحته |
position: 'bottom' | الخانات السفلية فقط: شريط أسفل عمود أو صفحة، يقصّر العمود فوقه |
position: 'here' | يُخرج المورد من التعويم: يُضمَّن المورد في التدفق عند توجيهه ::resource، في الموضع الذي يظهر فيه من التدفق بالضبط |
span: 'column' | يشغل الشريط عمودًا واحدًا (وفي الصفحة المفتوحة حديثًا يختار المحرّك العمود الذي بقيت فيه أكبر مساحة) |
span: 'page' | يمتد الشريط على عرض المحتوى كاملًا عبر كل الأعمدة، فيقطع تدفق الأعمدة؛ وتُحجز الأشرطة الممتدة على العرض الكامل أولًا، فتستقر العناصر العائمة الممتدة على عمود داخل المساحة المتبقية |
span: 'side' | يُنضَّد المورد في العمود الجانبي من تخطيط عمود ونصف يخصّص ذلك العمود للعناصر العائمة (layout.)، مرصوصًا بجوار الفقرة التي تحيل إليه أولًا. وفي صفحة بلا عمود كهذا يتصرف مثل 'column' |
في ResourcePlacement حقول أخرى غير هذين: rotate وwidth وalign وcolumns وcaptionSide وshrink وminScale وcaptionMeasure وwrap وwrapGap وcitingPage. يشرح الإعدادات › أنواع الموارد وصيغة المستند › الموضع عمل كل واحد منها.
- الوضع المؤجَّل: العنصر العائم الذي لا يتسع في أي خانة من الصفحة الحالية ينتظر الصفحة التالية التي يفتحها التدفق، وعند حدّ فصل يُفرَّغ على صفحات تُفتح قبل الحدّ. تحتفظ الصورة بحجمها ما لم يسمح
placement.shrink(أوlayout.floatShrinkللمستند كله) بتصغيرها إلى الحيّز المتاح في خانة. والجدول الأطول من أن تتسع له صفحة جديدة يُقطع بين الصفوف ويتابَع في الصفحات التالية مع تكرار صفوف رأسه (tableStyle.overflow: 'split'، القيمة الافتراضية) - المُخرج: موارد موضوعة في أشرطة الصفحة (
page.floats)، مع إنقاص ارتفاعات الأعمدة المتأثرة كي يتدفق النص حول الأشرطة
في وضع الموارد يصير الأمر أدق، لأن العناصر العائمة لا تشغل المساحة فحسب، بل تعيد تشكيل المساحة حولها. حين تستقر الكتلة التي تحمل الإحالة، تُعرض على العنصر العائم المعلّق الخاناتُ الحرّة في الصفحة الحالية بعدها: أسفل ذلك العمود، ثم أعلى العمود الفارغ التالي وأسفله (وللعنصر العائم الممتد على الصفحة: أسفل الصفحة، حين يبقى في كل عمود متسع)؛ وما لا يتسع في أي مكان ينتظر الصفحة التالية، حيث تحجز العناصر العائمة المعلّقة أشرطتها قبل أن يتدفق أي نص. تنكمش الأعمدة لتتسع بين الأشرطة، ويتدفق النص عبر الأعمدة المضيّقة دون انقطاع، ويرى القارئ الشكل قرب النقطة التي ذُكر فيها (لا عندها تمامًا). هذه ممارسة معتادة في التنضيد المهني، والكتب تفعلها باستمرار.
قواعد الوضع. إلى جانب توزيع الوضع، تتبع العناصر العائمة قيودًا تحريرية صارمة:
- قاعدة ما بعد الإحالة. يستقر العنصر العائم في أول خانة حرّة بعد أول إحالة إليه في النص، ولا يسبقها أبدًا. يصادف القارئ الإحالة أولًا، ثم يرى المورد. وإن لم يتسع العنصر العائم، يتأجل إلى الأمام إلى خانة أو صفحة لاحقة، لا إلى الخلف أبدًا.
- ترتيب الإحالات داخل السلسلة. تُعرض كل خانة على العناصر العائمة المعلّقة بترتيب أول إحالة، والعنصر الذي لا يتسع في أي مكان يؤخّر ما خلفه في سلسلة ترقيمه: الجدول 3 لا يستقر أبدًا بعد الجدول 4، والشكل 12 لا يسبق الشكل 11 أبدًا. ولا تؤخّر السلاسل بعضها بعضًا: الجدول المنتظر يسمح بمرور شكل لاحق. وحتى لا يضطر جدول طويل إلى انتظار صفحة جديدة، فإن الجدول الذي يُعرض عليه رأس عمود فارغ يُقطع على مقاس ذلك العمود ويُكمَل في الخانة التالية (العمود المجاور، أو أشرطة الصفحة التالية)، مع تكرار صفوف ترويسته.
- حاجز الفصل. لا تفلت العناصر العائمة من فصلها أبدًا. عند افتتاح فصل (مستوى عنوان مع
breakBeforeأوspan: 'page')، و:::part، ونمط إطار معfloatBarrier: true، ونهاية المستند، يوضع كل عنصر عائم معلّق أولًا (في الخانات الحرّة للصفحة، ثم على صفحات تُفتح قبل الحدّ، تضع كل منها قسرًا عنصرًا عائمًا واحدًا على الأقل) قبل فاصل الصفحة الخاص بالحدّ. وقبل فتح مثل هذه الصفحة، تُعرض على أي شكل أو جدول لا يزال معلّقًا الخاناتُ الحرّة في الصفحة الحالية مرة أخرى أيًّا كانت قيمةpositionفيه: العنصر العائم المخصّص لرأس الصفحة والمُحال إليه في الصفحة الختامية للفصل يأخذ أسفل تلك الصفحة، تحت الأعمدة الموزونة، بدلًا من صفحة خاصة به (وتحتفظ الإطارات العائمة بوضعها). ويرسل:::pagebreakالعناصر العائمة المعلّقة إلى الصفحة التي تليه، بعد أي حشو لضبط زوجية الصفحة. - الحد الأدنى لمساحة النص. في الصفحة المفتوحة حديثًا لا يُحجز شريط إلا إذا بقي متسع لـ 3 أسطر متن على الأقل في الأعمدة المتأثرة، مع استثناء واحد: يجوز وضع عنصر عائم مفرط الحجم قسرًا في شريط لا يزال كله نصًا، كي لا يعطّل شكل مهيمن الطابور إلى الأبد. وأما الخانة في الصفحة الحالية فيجب أن تتسع في الارتفاع المتبقي للعمود؛ ولا تنطبق قاعدة الأسطر الثلاثة هناك إلا بجوار شريط عائم آخر.
- مسافة للتنفس. تفصل فجوةٌ بارتفاع سطر متن واحد الشريطَ عن النص المجاور له.
- المحاذاة مع شبكة خطوط الأساس. تُقرَّب الأشرطة العلوية صعودًا إلى مضاعف لشبكة خطوط الأساس (فتكبر الفجوة تحت العنصر العائم)، فيقع كل سطر مُزاح على الشبكة. وتُثبَّت العناصر العائمة السفلية بحيث يقع آخر خط أساس في التعليق على الشبكة: يشترك التعليق في خط أساسه مع آخر سطر نصي في الأعمدة المجاورة، وتنتهي الصفحات عند الارتفاع نفسه عبر الأعمدة والصفحات المتقابلة.
الترقيم حسب النوع عند أول إحالة. لا تُرقَّم الموارد في markdown. يخصّص pipeline/resourceNumbering.ts لكل مورد رقمه عند أول إحالة إليه بترتيب القراءة، مستعملًا ResourceType الخاص بالمورد: يجمع numberingTemplate بين عدّاد النوع {n} وعدّادات العناوين {h1}..{h6} السارية عند الإحالة (مثلًا '{h1}.{n}' ← «1.7»)، ويتحكم resetOn في موعد إعادة العدّاد إلى البداية ('never' أو عند أي مستوى عنوان)، ويختار counterFormat الأرقام العشرية أو الرومانية أو الأبجدية. ويأتي النوعان المدمجان الشكل والجدول من defaultResourceTypes(locale)، مترجمَين إلى لغة المستند. ولأن الترقيم يتبع ترتيب أول إحالة، فإن إدراج شكل جديد في وسط المستند يعيد ترقيم كل ما بعده تلقائيًا، دون أي تعديل في المصدر.
#المرحلة 5: الكسر وقواعد الإبقاء معًا
هذه المرحلة هي ما يميّز محرّك الإخراج من أداة تفرغ النص على الصفحة. تفرض قواعد الجودة التحريرية التي طبّقها المنضّدون المحترفون يدويًا منذ قرون، والتي يتجاهلها رسم النص الساذج تمامًا.
تتقاسم العملَ ثلاث آليات، تعمل كل منها حيث توجد المعلومة التي تحتاج إليها: جزاء الكلمة المعزولة داخل كاسر الأسطر، وقسمة تُقيَّم بالدرجات حيث تبلغ الفقرة نهاية العمود، وقواعد الإبقاء معًا البنيوية بين الكتل.
الأرامل والأسطر اليتيمة والكلمات المعزولة
الأرامل والأسطر اليتيمة أوضح علامات التنضيد غير المحترف:
- الأرملة (widow) سطر واحد من فقرة تُرك وحده أسفل عمود. تكمل الفقرة في العمود التالي، لكن ذلك السطر الوحيد يبدو عالقًا (كأن العمود انتهى قبل أوانه).
- السطر اليتيم (orphan) سطر واحد من فقرة عالق أعلى عمود. معظم الفقرة في العمود السابق، لكن سطرًا واحدًا انسكب إلى ما بعده (فيبدو منفصلًا عن سياقه).
- الكلمة المعزولة (runt) فقرة سطرها الأخير كلمة قصيرة واحدة (أو اثنتان)، أقصر بصريًا من أن يبدو سطرًا نصيًا حقيقيًا. أقل خطورة بنيويًا من الأرملة، لكنها مزعجة بالقدر نفسه للقارئ المتأني.
تُحسم الكلمات المعزولة في كاسر الأسطر. الكلمة المعزولة شأن الفقرة وحدها: سطرها الأخير بالقِصر نفسه أينما استقرت الفقرة. ولذلك فهي القاعدة الوحيدة من الثلاث التي يراها كاسر الأسطر Knuth-Plass. حين تُنهي مجموعةُ كسور الفقرةَ بسطر محتواه أضيق من runtMinCharacters × normalSpaceWidth (القيمة الافتراضية لـ runtMinCharacters هي 20: محتوى بعرض عشرين مسافة بين الكلمات، أي نحو 8 إلى 12 حرفًا)، يضيف الكاسر runtPenalty (الافتراضي 1000) إلى رداءة ذلك السطر داخل صيغة نقاط الجزاء المربّعة، فتتنافس على المقياس نفسه الذي تتنافس عليه رداءة السطر (التي تتشبع عند 10000) بدلًا من أن تتضاءل أمامها. يجتمع هذا الجزاء مع نقاط الجزاء المعتادة (الرداءة، وكلفة تقسيم الكلمات، وعدم تطابق فئة الملاءمة) في تحسين شامل واحد، ويجد الكاسر في أغلب الأحوال مجموعة كسور بلا كلمة معزولة. وحيث لا يجدها، يُنضّد bodyText.tightenRunts الفقرة بسطر أقل حين تستطيع المسافات بين كلماتها، مع قليل من التتبّع السالب، أن تستوعب الفرق. تسلّم وحدة القياس الجزاء إلى الكاسر (measure/plain.ts، measure/rich.ts) ويطبّقه knuthPlass/breakpoints.ts.
تُحسم الأرامل والأسطر اليتيمة عند نهاية العمود. يكسر كاسر الأسطر الفقرة لعرض معيّن. لا يعرف أين ستقع الفقرة في الصفحة، فلا يستطيع أن يعرف أي أسطرها سيختم عمودًا. أما تمريرة التوزيع فتعرف. حين لا تتسع أسطر الفقرة كلها في ما بقي من العمود، يجرّب chooseParagraphSplit (في pipeline/orphanWidow.ts) كل قسمة k للأسطر المكسورة سلفًا، من 0 إلى عدد الأسطر التي تتسع: يبقى k من الأسطر ويفتتح الباقي العمود التالي. وتُحسب لكل مرشّح درجة:
slackWeight · slack²للأسطر التي يتركها فارغة أسفل العمود، وslackهو عدد الأسطر التي تتسع ناقصًاk(القيمة الافتراضية لـslackWeightهي 10)؛- مضافًا إليها
widowPenalty(الافتراضي 1000) حين يترك أقل منwidowMinLinesسطرًا أسفل العمود؛ - ومضافًا إليها
orphanPenalty(الافتراضي 1000) حين ينقل أقل منorphanMinLinesسطرًا إلى أعلى العمود التالي.
تفوز القسمة ذات أدنى درجة، وk = 0 تنقل الفقرة كاملة. القواعد ليّنة، والأرقام تبيّن مقدار لينها: جزاء قدره 1000 يساوي كلفة الفراغ لعشرة أسطر فارغة (10 · 10² = 1000)، فمع القيم الافتراضية تفوز القسمة التي تحترم القاعدتين كلما تركت أقل من عشرة أسطر فارغة في العمود. وبعد هذا الحد قد تكون الأرملة أو السطر اليتيم الخيار الأرخص. تختار القسمة من بين الأسطر التي صنعها كاسر الأسطر سلفًا؛ ولا تكسر الفقرة من جديد. وتمرّ عناصر القوائم بالقسمة نفسها عبر avoidOrphansInLists وavoidWidowsInLists، وتأخذ جزاء الكلمة المعزولة عبر avoidRuntsInLists (كلها true افتراضيًا).
كل هذا قابل للضبط في BodyTextConfig؛ راجع الإعدادات › الأسطر اليتيمة والأرامل والكلمات المعزولة وقواعد الإبقاء معًا. وضبط أي *Penalty على 0 يعطّل تلك القاعدة فعليًا.
قواعد الإبقاء معًا البنيوية
بعض التجميعات أكبر من فقرة واحدة: تمتد على كتل متجاورة ولا يمكن معالجتها بكسر الأسطر وحده. تفرض تمريرة التوزيع هذه القواعد حين تستقر كل كتلة، فتنقل مجموعات كاملة إلى الأمام حين كانت ستنقسم عبر فاصل عمود أو صفحة:
- العنوان مع فقرته الأولى. يجب ألا يظهر العنوان أبدًا أسفل عمود إذا كانت الفقرة التي يقدّمها ستبدأ في العمود التالي. يفرض ذلك
headings.keepWithNext(الافتراضيtrue): إذا لم يتسع المكان للعنوان مع الحد الأدنى لأسطر الأرملة في المتن (bodyText.widowMinLines، الافتراضي2) من الكتلة التالية (أو سطر واحد فقط حين يكونavoidWidowsمعطّلًا)، يُدفع العنوان إلى الأمام ليرافق نصه. - العناوين المتتالية. حين تظهر عدة عناوين متتابعة (مثلًا h2 يليه h3 تليه فقرة)، يجب أن تبقى المجموعة كلها معًا. ولا يجوز أن يُترك أي من العناوين عالقًا أسفل عمود دون المحتوى الذي يقدّمه.
- القوائم التي تقدّمها نقطتان رأسيتان. حين تنتهي فقرة بنقطتين رأسيتين تقدّمان قائمة مباشرة، يجب أن يبقى السطر الحامل للنقطتين مع بداية القائمة. يفرض ذلك
bodyText.keepColonWithList(الافتراضيtrue): إذا كان وضع الفقرة لن يترك متسعًا لبدء أول عنصر في القائمة (كاملًا حين تُبقيه قواعد الأسطر اليتيمة والأرامل للقوائم كاملًا؛ وسطرًا واحدًا معbodyText.colonListRoom: 'line'، كما كان حتى postext 1.4)، ينتقل السطر الأخير الحامل للنقطتين (أو الفقرة كلها، إن كانت سطرًا واحدًا) إلى الأمام مع القائمة. وكلما اضطرت هذه القاعدة إلى دفع الفقرة كلها وسبقتها مباشرة في العمود سلسلة من العناوين، تُسحب تلك العناوين إلى الأمام أيضًا كي لا يُنتهكkeepWithNextبصمت؛ والاستثناء الوحيد حين لا يحتوي العمود إلا العنوان (أو العناوين) الذي دفعه نقل سابق إلى الأمام، وعندها يُبقي المحرّك الفقرة مع العنوان ويقبل الفصل الأخف بين النقطتين والقائمة تجنبًا للدوران بلا نهاية. - الشكل مع تعليقه. الشكل وتعليقه وحدة لا تنفصل، ويتحركان معًا دائمًا.
حين يُكتشف انتهاك لقاعدة إبقاء معًا، يدفع المحرّك المجموعة كلها إلى العمود أو الصفحة التالية. فينتهي العمود الذي غادرته قصيرًا، بمقدار الأسطر التي كانت المجموعة ستشغلها. وملؤه مهمة موازنة الأعمدة.
هذه التصحيحات غير مرئية حين تُتقن (لا ينبغي أن يلاحظها القارئ أبدًا). لكن غيابها واضح فورًا لكل من يقرأ بانتباه: ذلك السطر الوحيد المحرج أعلى عمود، وتلك الفجوات غير المتساوية حيث كفّ المحرّك عن محاولة جعل النص يتسع. لدى الناشرين المحترفين أدلة أسلوب كاملة لمنع هذه المشكلات بالذات. وPostext يؤتمتها.
#مشغّل التمريرة
buildDocument مشغّل (driver) يحيط بتمريرة التوزيع (buildDocumentGen وbuildDocumentBalanced، في pipeline/build.ts). يشغّل التمريرة مرة واحدة. ثم يقرأ ما أبلغت عنه التمريرة، وحيث يحتاج قرار إلى نظرة ثانية يشغّل التمريرة من جديد، كاملةً، مع تلميحات (PassHints): السقوف والأسطر الإضافية والأرقام التي على التمريرة التالية أن تطبّقها. وحين لا ينشأ أي من شروط الجدول أدناه، تكون تلك التمريرة الأولى هي البناء كله.
تأتي إعادات التشغيل في جولات، كل جولة لنوع واحد من القرارات، بهذا الترتيب:
| الجولة | تجري حين | ما تُبلَّغ به التمريرة التالية | الحد |
|---|---|---|---|
| 1. المحتويات | يطبع المستند أرقام صفحات لم يقدّمها المضيف: :::toc أو :::index بلا outline في المحتوى، أو إحالة تطبع صفحة مرساة | المخطّط (outline) بتسميات صفحات البناء السابق | MAX_TOC_ROUNDS: 3 عمليات بناء بعد الأولى |
| 2. أرقام الحواشي | قيمة footnotes.numbering هي 'page' أو 'column' أو 'spread' والحواشي تقف أسفل العمود أو أسفل الصفحتين المتقابلتين، فيتوقف رقم الحاشية على الموضع الذي يضعها فيه الإخراج | الرقم الذي نالته كل حاشية في البناء السابق (footnoteNumbers) | MAX_FOOTNOTE_ROUNDS: 3 عمليات بناء بعد الأولى |
| 3. التعليقات الجانبية | إطار جانبي أو عنصر عائم جانبي تراكب مع التعليق الذي نضّده شكل في العمود الجانبي (placement.captionSide) | تنزل تلك التعليقات تحت أشكالها (captionUnder) | 3 تمريرات |
| 4. العناصر العائمة في صفحة الإحالة | عنصر عائم مع placement.citingPage استقر في صفحة أو عمود بعد السطر الذي يحيل إليه | تُفتتح تلك الصفحة، أو ذلك العمود، بالعنصر العائم في رأسه (citingPageFloats) | MAX_CITING_PAGE_ROUNDS: 3 جولات من الاقتراحات، ثم تمريرة لكل دفعة من العناصر العائمة لم تستطع تمريرة إبقاءها هناك |
| 5. سقوف الأشرطة | كتلة ممتدة على عرض الصفحة وصلت إلى شريط أعمدته غير مستوية (راجع المرحلة 3) | الارتفاع الذي يُقطع عنده ذلك الشريط (bandCaps) | MAX_BAND_PASSES: 4 تمريرات من الاقتراحات وإعادة المحاولات. يزيد السقف سطرًا حتى 3 مرات (MAX_BAND_CAP_RETRIES)، ثم يُتخلى عنه |
| 6. موازنة الأعمدة | headings. مفعّل وعمود قابل للموازنة ينتهي قصيرًا | أسطر شبكة إضافية لكل كتلة والفقرات التي تُكسر بسطر زائد (balanceExtraPx، balanceLooseness) | MAX_BALANCING_PASSES: 10 محاولات لكل مقطع. MAX_BALANCING_PASSES_PER_DOCUMENT: 30 تمريرة لكل مستند |
| 7. الأشرطة الختامية | الموازنة مفعّلة مع headings.، وفصل أو المستند ينتهي في شريط أعمدته غير مستوية | سقف ختامي لذلك الشريط، مع تجميد تلميحات الموازنة | MAX_BAND_PASSES: 4 تمريرات من الاقتراحات وإعادة المحاولات، ولكل سقف 3 إعادات محاولة |
| 8. التحسين | احتفظت الجولة 7 بسقف ختامي | موازنة الأعمدة مرة أخرى، على الإخراج المسقوف، بمقاطع جديدة | 10 محاولات لكل مقطع و30 تمريرة، يبدأ عدّها من جديد |
تجري الجولات من 3 إلى 8 واحدة بعد أخرى داخل بناء واحد (buildDocumentBalanced). أما الجولتان 1 و2 فخارجه: جولة المحتويات أو جولة أرقام الحواشي بناء كامل، له جولاته الخاصة من 3 إلى 8. لا حدّ للبناء في مجموعه، بل هذه الحدود وحدها.
كل إعادة تشغيل توزيع كامل. التلميح لا يرقّع شجرة التمريرة السابقة. تضع التمريرة المستند من جديد والتلميح سارٍ، فالعنوان الذي فوقه سطران إضافيان، أو الشريط المقطوع أعلى بستة أسطر، يمرّ بقواعد الكسر والإبقاء معًا نفسها التي يمرّ بها كل شيء آخر. ثم يفحص المشغّل النتيجة: سقف الشريط الذي لم يستقر إطاره عند القطع يُزاد أو يُتخلى عنه، وتمريرة الموازنة التي لم تحسّن مقطعًا تُهمل لذلك المقطع. وما يعيده المشغّل مؤلَّف من صفحات وضعتها تمريرة: تمريرة واحدة بكاملها، أو، بعد موازنة الأعمدة، صفحات كل مقطع من أفضل تمريرة حظي بها ذلك المقطع.
ما يبلغ عنه المستند. يعدّ doc.iterationCount تمريرات التوزيع التي يقوم عليها الإخراج الذي يعيده المشغّل: الأولى وتمريرات جولات سقوف الأشرطة والموازنة والأشرطة الختامية. ولا تدخل فيه تمريرات التعليقات الجانبية وصفحة الإحالة، ولا عمليات البناء السابقة في جولة محتويات أو جولة أرقام حواشٍ؛ ويُستدعى BuildDocumentOptions.onPass لكل تمريرة، مع زمنها الفعلي. أما doc.converged فيخص موازنة الأعمدة وحدها. قيمته true حين ينتهي كل مقطع بلا سطر فارغ في أعمدته القابلة للموازنة أو بلا أداة باقية يقترحها، وfalse حين يتوقف مقطع عند تمريرة لم تحسّنه، أو تنفد محاولاته، وفيه فجوة ما زالت مفتوحة. ومع تعطيل الموازنة قيمته true. والمستند الذي فيه converged: false مستند كامل: يحمل أفضل إخراج وجده المشغّل، وأحد أعمدته ينتهي قصيرًا.
#موازنة الأعمدة
- تجري: في الجولتين 6 و8 من المشغّل (مفعّلة افتراضيًا؛ ويعطّلها
headings.balancing.enabled: false، وهي معطّلة افتراضيًا في النص العمودي وفي شبكة المحارف، انظر الإعدادات › موازنة الأعمدة) - المُدخل: أفضل إخراج حتى الآن وأسطر الشبكة الفارغة أسفل كل عمود قابل للموازنة (
collectColumnGaps، فيpipeline/columnBalancing.ts) - العمل: اقتراح أدوات (levers) تملأ كل عمود قصير في مكانه، وتشغيل تمريرة التوزيع بها، والاحتفاظ بالنتيجة لمقطع حين تنخفض درجة فجواته
- المُخرج: أعمدة تنتهي عند آخر سطر في شبكة الصفحة، مع تسجيل كل أداة على الكتلة التي طُبّقت عليها (
VDTBlock.balancing)
تلاحظ الأعمدة غير الموزونة فورًا. وقواعد الكسر تتركها على الدوام: عنوان انتقل ليبقى مع نصه، وفقرة انتقلت كاملة تجنبًا لأرملة، وشكل لم يتسع. العمود الذي يتوقف قبل العمود المجاور بسطرين يبدو غير مكتمل، كأن الإخراج توقف في منتصف الطريق.
لا تغيّر أي كتلة عمودها لإصلاح ذلك. ففاصل العمود مرتبط بالعنصر الذي لم يتسع: الكتلة التي تفتتح العمود التالي كانت أطول من المتسع الذي بقي، وهي ما زالت أطول من متسع أصغر. لذلك يُملأ العمود القصير من داخله، بمقدار فجوته على الأكثر، ولا يعبر شيء إلى العمود التالي. التعديل موضعي، وهو ما يفعله المنضّد بيده: يضيف سطرًا من المسافة حيث تقبله العين. يكون العمود قابلًا للموازنة حين يتدفق المحتوى بعده إلى عمود لاحق في الصفحة نفسها، أو، للعمود الأخير في الصفحة، حين يستمر تدفق الصفحة إلى التالية. ويجوز أن تنتهي الصفحة الأخيرة من الفصل قصيرة؛ فشريطها الختامي يقطعه سقف ختامي قطعًا مستويًا (راجع المرحلة 3).
لكل فجوة تُقترح الأدوات بترتيب الأولوية التحريرية، وهو الترتيب نفسه الذي تسردها به الإعدادات › موازنة الأعمدة:
- إطار يختم العمود يأخذ المتسع الذي تحت قاعدته مسافةً فوقه، فتقع قاعدته على آخر سطر في شبكة الصفحة، بمحاذاة آخر سطر في العمود المجاور. ويُسجَّل باسم
trailingCallout. - الصور ذات المنطقة الآمنة (
Resource.safeArea)، المضمّنة في العمود أو العائمة في رأسه أو أسفله وفوقه وحده، تطول بأسطر كاملة من الشبكة، مقصوصة داخل منطقتها الآمنة. والصورة الأطول لا تترك فجوة في الصفحة، ولذلك تعمل هذه الأداة قبل إضافة أي مسافة. وتُسجَّل باسمflexFigure. - العناوين تأخذ أسطرًا كاملة من الشبكة فوقها، سطرًا واحدًا في كل دور، والعنوان الأهم (صاحب أصغر رقم مستوى) أولًا، فينال الحصة الكبرى. والعنوان الواقع في أعلى العمود تمامًا لا يأخذ شيئًا، فتظل الأعمدة تبدأ عند أعلى الصفحة. ولا يأخذ العنوان بهذه الطريقة إلا أسطره الأولى قبل تجربة الأدوات التالية، ثم يأخذ المزيد، حتى حدّه الأقصى، حين لا يبقى لديها ما تعطيه. وتُسجَّل باسم
heading. - نهايات القوائم تأخذ سطرًا من الشبكة حيث تنتهي قائمة. وتُسجَّل باسم
listEnd. - تحت المعادلات والإطارات والأشكال يُضاف سطر من الشبكة: تحت معادلة مستقلة أو إطار يُستأنف النص بعده (
afterDisplay)، وبين شكل أو جدول يتصدّر العمود والنص الذي تحته (afterFloat). - الفقرات المرخاة آخر الأدوات. تُكسر فقرات العمود، الأطول أولًا، كلٌّ منها بسطر زائد (
\looseness=+1في TeX، عبر الخيارloosenessفي كاسر Knuth-Plass)، ولا يُقبل التنضيد الأطول إلا ما دام كل سطر ضمنbodyText.maxWordSpacing. وحين لا يكفي تباعد الكلمات وحده لكسب السطر، يجوز للفقرة أن تأخذ أيضًا أصغر تتبّع موجب يحقق ذلك، دون تجاوز حدّ أقصى. وتُسجَّل باسمlooseParagraph.
الحدود القصوى وخيارات التفعيل والاستثناءات (شبكة المحارف، والصفحات الختامية، وعنصر عائم فوق عدة أعمدة) كلها مفاتيح في headings.balancing. تسردها صفحة الإعدادات وتُحدَّث معها؛ وهذه الصفحة لا تكررها.
يُختبر الاقتراح بتوزيع المستند به. التمريرة التي تطبّق الأدوات توزيع كامل، وبين حين وآخر تنقل أداةٌ محتوى عبر فاصل عمود مع ذلك: فقرة مقسومة لم يعد رأسها يتسع، أو عنصر عائم فقد خانته. فتتدفق الصفحات بعد تلك النقطة على نحو مختلف وتنفتح فجوات في مواضع أخرى. يرى المشغّل ذلك في الدرجة، فيهمل التمريرة لذلك المقطع، ويضع في القائمة السوداء الأدوات المطبّقة حديثًا في أول عمود تغيّر، فيُبقي الاقتراح التالي على الأدوات التي نجحت ويستبعد الأداة التي امتد أثرها إلى ما بعد عمودها.
تتقارب الموازنة لكل مقطع على حدة. الصفحات الواقعة بين الفواصل الصريحة (افتتاح فصل، أو :::pagebreak) تُخرَج مستقلة بعضها عن بعض: لا شيء يتدفق عبر مثل هذا الفاصل، فلا يمكن لأداة موازنة داخل مجموعة من الصفحات أن تحرك سطرًا من مجموعة أخرى. ولذلك تحكم حلقة موازنة الأعمدة على كل مقطع (segment) من هذا النوع بمفرده. ودرجة فجواته هي عدد أسطر الشبكة الفارغة أسفل أعمدته القابلة للموازنة. لا تزال التمريرة الواحدة تضع المستند كله، لكن كل مقطع يحتفظ بنصيبه من أدوات الموازنة أو يرفضه وفق درجة الفجوات الخاصة به، ويضع سلاسل التتابع (cascades) الخاصة به في القائمة السوداء، ويبلغ حالة الثبات وحده، وينفق ميزانيته الخاصة من المحاولات؛ والمقطع الذي تراجعت نتيجته بعد إعادة المحاولة يستعيد صفحاته من أفضل تمريرة حظي بها بينما تمضي المقاطع الأخرى. وهكذا يتوازن كتاب من ثلاثين فصلًا تمامًا كما تتوازن فصوله واحدًا واحدًا (صفحات الفصل في ملف PDF للكتاب كله مطابقة لملف PDF لذلك الفصل وحده)، بدلًا من أن تكلّف سلسلة تتابع واحدة في أي مكان كل صفحات الكتاب تمريرة إضافية.
الحدود صمام أمان. بعض الأعمدة لا يمكن إغلاقها: العمود الذي لا يحمل إلا فقرات قصيرة لا أداة له، والعمود الذي تدفع كلُّ أداة من أدواته كتلةً عبر فاصل العمود ليس فيه أداة يمكن الاحتفاظ بها. يحتفظ المحرّك بأفضل إخراج وجده ويترك ذلك العمود قصيرًا. ولأن الأدوات مسجّلة على الكتل، تستطيع بروفة أو اختبار أن يسرد الأعمدة التي لم يغلقها شيء (راجع الإعدادات › أيّ أداة عملت).