البحث العميق في RenderingNG: تجزئة كتلة LayoutNG

اكتملت الآن كتلة التقسيم في LayoutNG. تعرَّف على طريقة عملها وسبب أهميتها في هذه المقالة.

Morten Stenshorne
Morten Stenshorne

اسمي "مورتن ستينثورن"، وأنا مهندس تنسيق في فريق عرض Blink في Google. لقد انخرطت في تطوير محركات المتصفح منذ أوائل العقد الأول من القرن الحادي والعشرين، واستمتعت كثيرًا بالمساعدة، مثل المساعدة في اجتياز اختبار acid2 في محرك Presto (الإصدار 12 من Opera والإصدارات الأقدم)، وإجراء الهندسة العكسية على المتصفحات الأخرى لإصلاح تصميم الجدول في Presto. أمضيت أيضًا في تلك السنوات أكثر مما أود الإقرار به في ما يتعلّق بتقسيم الكتل، لا سيما العناصر المتعددة في Presto وWebKit وBlink. خلال السنوات القليلة الماضية في Google، كنتُ أركّز بشكل أساسي على قيادة عملية إضافة دعم تجزئة الكتل إلى LayoutNG. انضم إليّ في هذا الدرس التفصيلي عن تنفيذ تجزئة الكتل، فقد تكون هذه هي المرة الأخيرة التي أنفذ فيها تجزئة الكتل. :)

ما المقصود بتقسيم الكتل؟

يتعلق تقسيم الكتل بتقسيم مربّع على مستوى كتلة CSS (مثل قسم أو فقرة) إلى أجزاء متعدّدة عندما لا يتلاءم مع الكل داخل حاوية أجزاء واحدة تُسمى أداة تقسيم الأجزاء. أداة التجزئة ليست عنصرًا، ولكنها تمثل عمودًا في تخطيط متعدد الأعمدة أو صفحة في الوسائط المقسّمة على صفحات. لإجراء التقسيم، يجب أن يكون المحتوى داخل سياق التجزئة. ويتم إنشاء سياق التقسيم في أغلب الأحيان بواسطة حاوية متعددة الأعمدة (سيتم تقسيم المحتوى إلى أعمدة)، أو عند الطباعة (سيتم تقسيم المحتوى إلى صفحات). قد يلزم تقسيم الفقرة الطويلة التي تحتوي على العديد من الأسطر إلى أجزاء متعددة، بحيث يتم وضع الأسطر الأولى في الجزء الأول، ويتم وضع الأسطر المتبقية في الأجزاء التالية.

فقرة من نص مقسمة إلى عمودين.
في هذا المثال، تم تقسيم فقرة إلى عمودين باستخدام تنسيق متعدد الأعمدة. ويمثّل كل عمود مجزأً يمثّل جزءًا من التدفق المجزأ.

تشبه تجزئة الأسطر نوعًا آخر معروفًا من أجزاء التجزئة، وهي تجزئة الأسطر (والتي تُعرف أيضًا باسم "كسر الأسطر"). أي عنصر مضمّن يتألف من أكثر من كلمة واحدة (أي عقدة نصية وأي عنصر <a> وما إلى ذلك)، ويسمح بفواصل الأسطر، يمكن تقسيمه إلى أجزاء متعددة. يتم وضع كل جزء في مربع خط مختلف. مربّع السطر هو عبارة عن تجزئة مضمّنة تعادل أداة تجزئة للأعمدة والصفحات.

ما المقصود بتقسيم كتلة LayoutNG؟

إن LayoutNGBlockFragmentation هي إعادة كتابة لمحرك التجزئة لـ LayoutNG، وبعد سنوات عديدة من العمل، تم أخيرًا شحن الأجزاء الأولى في إصدار Chrome 102 في وقت سابق من هذا العام. وقد أصلحنا هذه المشاكل طويلة الأمد والتي لم يتم إصلاحها على الإطلاق في محرّك البحث "القديم". في ما يتعلّق ببُنى البيانات، يتم استبدال بُنى بيانات متعددة قبل NG باستخدام أجزاء NG الممثَّلة مباشرةً في شجرة البيانات.

على سبيل المثال، نتيح الآن استخدام قيمة "aتجنُّب" لسمتَي CSS "break-before" و"break-after"، ما يتيح للمؤلفين تجنُّب الفواصل بعد العنوان مباشرةً. ولا يبدو هذا الأمر جيدًا بصفة عامة إذا كان آخر شيء يتم وضعه على الصفحة هو عنوان، بينما يبدأ محتوى القسم من الصفحة التالية. بدلاً من ذلك، من الأفضل التقسيم قبل العنوان. انظر الشكل أدناه للاطّلاع على مثال.

يوضح المثال الأول عنوانًا أسفل الصفحة، والثاني يعرضه في أعلى الصفحة التالية مع المحتوى المرتبط بها.

يتوافق Chrome 102 أيضًا مع تجاوز التقسيم، بحيث لا يتم تقسيم المحتوى المتجانس (المفترض أن يكون غير قابل للكسر) إلى أعمدة متعددة، ويتم تطبيق تأثيرات الطلاء مثل التظليل وعمليات التحويل بشكل صحيح.

اكتمل الآن حظر التقسيم في LayoutNG

في وقت كتابة هذا النص، أكملنا الدعم الكامل لتقسيم الكتل في LayoutNG. تم شحن التقسيم الأساسي (حاويات الحظر، بما في ذلك تنسيق الخط والأعداد العشرية والموضع خارج التدفق) التي تم شحنها في Chrome 102. يتم شحن أجزاء من مرنة وعلى الشبكة في Chrome 103، وتقسيم الجدول إلى Chrome 106. أخيرًا، يتم شحن الطباعة في Chrome 108. كان تجزئة الكتلة هي الميزة الأخيرة التي اعتمدت على المحرّك القديم لتنفيذ التنسيق. وهذا يعني أنّه اعتبارًا من الإصدار 108 من Chrome، لن يتم استخدام المحرّك القديم لإجراء التنسيق.

بالإضافة إلى عرض المحتوى، تتيح بُنى بيانات LayoutNG الرسم واختبار النتائج، ولكننا ما زلنا نعتمد على بعض بُنى البيانات القديمة لواجهات برمجة تطبيقات JavaScript التي تقرأ معلومات التنسيق، مثل offsetLeft وoffsetTop.

من خلال توضيح جميع الجوانب باستخدام NG، يمكن تنفيذ وتوزيع ميزات جديدة تتضمّن فقط عمليات تنفيذ LayoutNG (وليست نظيرتها للمحرّك القديم)، مثل طلبات البحث في حاوية CSS وتحديد موضع الإرساء وMathML والتنسيق المخصّص (Houdini). بالنسبة إلى الطلبات المتعلّقة بالحاويات، كنّا نشحنها بشكل مُسبق، مع تحذير للمطوّرين بأنّ الطباعة غير متاحة بعد.

تم شحن الجزء الأول من LayoutNG في عام 2019، والذي كان يتكون من تنسيق حاوية الكتل العادية، والتخطيط المضمّن، وعدد مرات عشرية، وتحديد الموضع خارج التدفق، ولكن لا يتوفر الدعم للثنية أو الشبكة أو الجداول، ولا يدعم على الإطلاق تقسيم الكتل. سنعود إلى استخدام محرك التخطيط القديم للأنماط المرنة والشبكة والجداول بالإضافة إلى أي شيء يتعلق بتقسيم الكتل. وكان ذلك صحيحًا حتى بالنسبة إلى عناصر القوالب والمضمَّنة والعائمة والخارجية ضمن المحتوى المجزأ - كما ترون، تُعَد ترقية محرك التخطيط المعقد كمثالٍ دقيق لهذه الترقية.

بالإضافة إلى ذلك، صدق أو لا تصدق، بحلول منتصف عام 2019، تم تطبيق غالبية الوظائف الأساسية لتخطيط تجزئة كتلة LayoutNG (خلف علامة). إذن، لماذا استغرق شحنه وقتًا طويلاً؟ الإجابة المختصرة هي: يجب أن يتزامن التقسيم بشكل صحيح مع أجزاء قديمة مختلفة من النظام والتي لا يمكن إزالتها أو ترقيتها إلى أن تتم ترقية جميع التبعيات. للحصول على الإجابة الطويلة، راجع التفاصيل التالية.

التفاعل مع المحرّك القديم

لا تزال بُنى البيانات القديمة مسؤولة عن واجهات برمجة تطبيقات JavaScript التي تقرأ معلومات التنسيق، لذا نحتاج إلى إعادة إرسال البيانات إلى المحرّك القديم بطريقة يفهمها. يتضمن ذلك تعديل بُنى البيانات القديمة متعددة الأعمدة، مثل LayoutMultiColumnFlowThread بشكل صحيح.

رصد الإجراء الاحتياطي للمحرّك القديم ومعالجته

كان علينا العودة إلى محرك التخطيط القديم عندما كان هناك محتوى بداخله لا يمكن التعامل معه بعد من خلال تجزئة كتلة LayoutNG. في وقت شحن كتلة LayoutNG الأساسية (ربيع 2022)، التي تضمّنت الخطة المرنة والشبكة والجداول وأي محتوى مطبوع. كان ذلك معقدًا بشكل خاص لأنّنا كنا بحاجة إلى رصد الحاجة إلى عنصر احتياطي قديم قبل إنشاء العناصر في شجرة التنسيق. على سبيل المثال، كنا بحاجة إلى الكشف قبل أن نعرف ما إذا كان هناك كيان أصل حاوية متعددة الأعمدة، وقبل أن نعرف عُقد DOM التي ستصبح سياقًا للتنسيق أم لا. إنها مشكلة بسيطة وليس لها حل مثالي، ولكن ما دام سلوكها الخاطئ هو النتائج الإيجابية الخاطئة (الرجوع إلى الإصدار القديم بدون الحاجة إلى ذلك)، فلا بأس، لأن أي أخطاء في سلوك التصميم هذا هي أخطاء موجودة في Chromium بالفعل، وليست أخطاء جديدة.

التنزّه سيرًا على الأقدام قبل رسم شجرة

على سبيل المثال، ننفّذ الطلاء المسبق بعد التصميم ولكن قبل الطلاء. التحدي الرئيسي هو أننا ما زلنا بحاجة إلى اتّباع شجرة كائنات التنسيق، ولكن لدينا أجزاء NG الآن، فكيف نتعامل مع ذلك؟ نمشي على كل من كائن التخطيط وأشجار جزء NG في نفس الوقت! هذا أمر معقد للغاية، لأن التخطيط بين الشجرتين ليس بالأمر الهين. على الرغم من أنّ بنية شجرة عناصر التنسيق تشبه إلى حدّ كبير بنية شجرة نموذج العناصر في المستند، فإنّ شجرة المجزّأة هي مصدر للتنسيق، وليست إدخالاً لها. بصرف النظر عن تأثير أيّ تجزئة، بما في ذلك التقسيم المضمّن (أجزاء الأسطر) وتقسيم الكتل (أجزاء العمود أو الصفحة)، تضمّ شجرة المجزّأ أيضًا علاقة مباشرة بين عنصر رئيسي وفرعي بين كتلة متضمّنة والعناصر التابعة لنموذج العناصر في المستند (DOM) التي تتضمّن هذا الجزء ككتلة متضمّنة فيها. على سبيل المثال، في شجرة الأجزاء، يكون الجزء الذي تم إنشاؤه من خلال عنصر في الموضع المطلق عنصرًا ثانويًا مباشرًا لجزء الكتلة الذي يحتوي عليه، حتى إذا كانت هناك عُقد أخرى في سلسلة الأصل بين العنصر التابع الذي يحمل الموضع خارج التدفق والكتلة المضمّنة فيه.

يصبح الأمر أكثر تعقيدًا عندما يكون هناك عنصر موضع خارج التدفق داخل التقسيم، لأن الأجزاء خارج التدفق تصبح عناصر فرعية مباشرة للمُجزئ (وليس عنصرًا فرعيًا لما تعتبره CSS أنّه الجزء المضمّن). كانت هذه مشكلة كان لا بد من حلها من أجل العمل مع المحرك القديم دون الكثير من المتاعب. في المستقبل، من المفترض أن يكون بمقدورنا تبسيط الكثير من هذه التعليمات البرمجية، لأن LayoutNG مصمم للعمل بمرونة مع جميع أوضاع التخطيط الحديثة.

المشاكل المتعلقة بمحرك التجزئة القديم

إن المحرك القديم، المصمم في عصر سابق من الإنترنت، ليس له مفهوم التجزئة في الواقع، حتى لو كان التقسيم موجودًا تقنيًا في ذلك الوقت أيضًا (من أجل دعم الطباعة). كان دعم التجزئة مجرد شيء يتم تثبيته في الأعلى (الطباعة) أو تعديله (متعدد الأعمدة).

عند وضع محتوى قابل للتقسيم، يضع المحرك القديم كل شيء في شريط طويل يصل عرضه إلى الحجم المضمّن للعمود أو الصفحة، ويكون الارتفاع بنفس الطول اللازم لتضمين محتواه. لا يتم عرض هذا الشريط الطويل على الصفحة، حيث يمكنك اعتباره على أنه يتم عرضه في صفحة افتراضية ثم تتم إعادة ترتيبه للعرض النهائي. تتشابه هذه الطريقة من الناحية النظرية مع طباعة مقالة بصحيفة ورقية كاملة في عمود واحد، ثم استخدام المقص لقصها إلى عدة أجزاء كخطوة ثانية. (في الماضي، كانت بعض الصحف تستخدم أساليب مشابهة بالفعل!)

يتتبّع المحرك القديم حدود صفحة أو عمود خيالية في الشريط. وهذا يتيح له دفع المحتوى الذي لا يتناسب مع الحد إلى الصفحة أو العمود التالي. على سبيل المثال، إذا كان النصف العلوي من الخط مناسبًا لما يعتبره المحرك هي الصفحة الحالية، فسوف يتم إدراج "عنصر تقسيم على صفحات" لدفعه إلى أسفل إلى الموضع الذي يفترض فيه المحرك أن الجزء العلوي من الصفحة التالية. ثم تحدث معظم أعمال التجزئة الفعلية ("قص باستخدام المقص والموضع") بعد التخطيط أثناء نسخ الأعمدة ورسمها بالكامل (عن طريق قص الأعمدة أو تقسيمها إلى صفحات). وقد جعل هذا الأمر مستحيلاً في الأساس، مثل تطبيق التحويلات وتحديد الموضع النسبي بعد التقسيم (وهو ما تتطلبه المواصفات). بالإضافة إلى ذلك، وعلى الرغم من توفير بعض الدعم لتقسيم الجداول في المحرك القديم، لا يوجد دعم مرن أو لتقسيم الشبكة على الإطلاق.

إليك صورة توضيحية لكيفية تمثيل تخطيط مكوَّن من ثلاثة أعمدة داخليًا في المحرّك القديم، قبل استخدام المقص والموضع والصمغ (لدينا ارتفاع محدّد، بحيث تتلاءم أربعة خطوط فقط، ولكن هناك بعض المساحة الزائدة في الجزء السفلي):

ويمثل التمثيل الداخلي كعمود واحد مع تقسيم على صفحات في مواضع فاصل المحتوى، والتمثيل الذي يظهر على الشاشة كثلاثة أعمدة.

نظرًا لأن محرك التخطيط القديم لا يقوم بتقسيم المحتوى بالفعل أثناء التخطيط، فهناك العديد من العناصر الغريبة، مثل الموضع النسبي والتحويل الذي يتم تطبيقه بشكل غير صحيح، واقتصاص الظلال المربع عند حواف الأعمدة.

فيما يلي مثال بسيط لظل النص:

لا يتعامل المحرك القديم جيدًا مع هذا الأمر:

تم وضع ظلال نصية مقطوعة في العمود الثاني.

هل ترى كيف يتم اقتصاص ظل النص من السطر في العمود الأول، ووضعه في أعلى العمود الثاني بدلاً من ذلك؟ ويرجع ذلك إلى أنّ محرك التخطيط القديم لا يفهم التقسيم!

من المفترض أن يبدو الأمر على النحو التالي (وهذه هي الطريقة التي يظهر بها مع NG):

عمودان من النص يعرض الظلال بشكل صحيح.

بعد ذلك، لنجعل الأمر أكثر تعقيدًا باستخدام التحويلات وتظليل المربع. لاحِظ كيف يوجد اقتصاص وهوامش غير صحيح في المحرك القديم. وذلك لأنه من المفترض أن يتم تطبيق التحويلات حسب المواصفات كتأثير ما بعد التخطيط والتقسيم اللاحق. مع تجزئة LayoutNG، يعمل كلاهما بشكل صحيح. يؤدي هذا إلى زيادة إمكانية التشغيل التفاعلي مع متصفح Firefox، الذي كان يوفر دعمًا جيدًا للتجزئة لبعض الوقت مع اجتياز معظم الاختبارات في هذا المجال هناك أيضًا.

يتم تقسيم المربعات بشكل غير صحيح عبر عمودين.

ويواجه المحرك القديم أيضًا مشكلات مع المحتوى المتجانس الطويل. يكون المحتوى متجانسًا إذا لم يكن مؤهَّلاً للتقسيم إلى أجزاء متعدّدة. العناصر التي تحتوي على تمرير كامل السعة متجانسة، لأنه ليس من المنطقي للمستخدمين التمرير في منطقة غير مستطيلة. تعتبر مربعات الخطوط والصور أمثلة أخرى على المحتوى المتجانس. وفي ما يلي مثال لذلك:

إذا كان المحتوى المتجانس طويلاً جدًا بحيث لا يمكن احتواؤه داخل عمود، سيقسّمه المحرك القديم بشكل وحشي (مما يؤدي إلى سلوك "مثير للاهتمام" عند محاولة التمرير في الحاوية القابلة للتمرير):

بدلاً من تركه يتجاوز العمود الأول (كما يحدث مع تجزئة كتلة LayoutNG):

ALT_TEXT_HERE

يدعم المحرّك القديم عرض الفواصل الإعلانية التي يتم فرضها. على سبيل المثال، ستُدرج السمة <div style="break-before:page;"> فاصل صفحة قبل عنصر DIV، إلا أنّها تتيح بشكل محدود فقط العثور على الفواصل الإعلانية الإجبارية الأمثل. ويتيح هذا الإصدار استخدام break-inside:avoid واليتام والأرامل، ولكن لا يمكن تجنُّب الفواصل بين مجموعات الإعلانات، إذا تم طلبها من خلال break-before:avoid على سبيل المثال. اطلع على المثال التالي:

تم تقسيم النص إلى عمودين.

في هذه الحالة، يحتوي العنصر #multicol على مساحة لـ 5 أسطر في كل عمود (لأنّه يبلغ طوله 100 بكسل، وارتفاع السطر هو 20 بكسل)، لذلك يمكن أن يناسب العمود الأول جميع #firstchild. في المقابل، تم استبعاد المحتوى الفرعي #secondchild من خلال عملية Breaked:aتجنُّب، ما يعني أنّ المحتوى لا يريد أن يحدث استراحة بينهما. بما أنّ قيمة widows هي 2، نحتاج إلى إرسال سطرَين من #firstchild إلى العمود الثاني، وذلك لتلبية جميع طلبات تجنب الفواصل. Chromium هو أول محرك متصفح يتوافق بشكل كامل مع هذه التركيبة من الميزات.

آلية عمل تجزئة NG

يعمل محرك تخطيط NG بشكل عام على تخطيط المستند من خلال اجتياز عمق شجرة مربع CSS أولاً. عندما يتم تخطيط جميع توابع العقدة، يمكن إكمال تخطيط تلك العقدة، من خلال إنتاج NGPhysicalFragment والعودة إلى خوارزمية التخطيط الرئيسية. تضيف هذه الخوارزمية الجزء إلى قائمة الأجزاء الفرعية، وبعد اكتمال جميع العناصر الثانوية، تنشئ جزءًا لنفسها مع جميع الأجزاء الفرعية بداخلها. من خلال هذه الطريقة، يتم إنشاء شجرة أجزاء للمستند بأكمله. في المقابل، يمثّل ذلك تبسيطًا مفرطًا: على سبيل المثال، يجب أن تتصاعد فقاعات العناصر الموضوعة خارج التدفق من مكانها في شجرة نموذج العناصر في المستند (DOM) إلى كتلة المحتوى قبل وضعها. أتجاهل هذه التفاصيل المتقدمة هنا بهدف التبسيط.

بالإضافة إلى مربع CSS نفسه، يوفر LayoutNG مساحة محدودة لخوارزمية التخطيط. يزود هذا الخوارزمية بمعلومات مثل المساحة المتاحة للتخطيط، وما إذا تم إنشاء سياق تنسيق جديد، ونتائج تصغير الهامش المتوسط من المحتوى السابق. تعرف مساحة القيد أيضًا حجم الكتلة المرسومة للكسر، وإزاحة الكتلة الحالية إليها. يشير هذا إلى مكان الاستراحة.

عند استخدام تجزئة الكتلة، يجب أن يتوقف تصميم العناصر التابعة عند الفاصل. تشمل أسباب الأعطال نفاد مساحة التخزين في الصفحة أو العمود أو فرض فاصل إعلاني. بعد ذلك، ننتج أجزاءً للعُقد التي انتقلنا إليها، ونعود إلى جذر المستند في سياق التقسيم (الحاوية المتعدّدة الأعمدة، أو جذر المستند في حال الطباعة). بعد ذلك، في جذر سياق التجزئة، نستعد لمجزأة جديد، وننزل إلى الشجرة مرة أخرى، ونستأنف من حيث توقفنا قبل الفاصل.

تُسمى بنية البيانات بالغة الأهمية لتوفير وسائل استئناف التنسيق بعد الفاصل NGBlockBreakToken. يحتوي على جميع المعلومات اللازمة لاستئناف التخطيط بشكل صحيح في أداة التقسيم التالية. يرتبط NGBlockBreakToken بعقدة معيّنة، ويشكل شجرة NGBlockBreakToken، بحيث يتم تمثيل كل عُقدة يجب استئنافها. يتم إرفاق NGBlockBreakToken بعنصر NGPhysicalBoxFragment الذي يتم إنشاؤه للعُقَد التي تقطع بالداخل. يتم نشر رموز الفاصل المميزة إلى العناصر الرئيسية، لتشكيل شجرة من الرموز المميزة للكسر. إذا احتجنا إلى التقسيم قبل عقدة (بدلاً من داخلها)، لن يتم إنشاء أي جزء، ولكن ستظل العقدة الرئيسية بحاجة إلى إنشاء رمز فاصل "فاصل قبل" للعقدة، حتى نتمكن من البدء في وضعها عندما نصل إلى الموضع نفسه في شجرة العقدة في المقسّم التالي.

يتم إدراج الفواصل عند نفاد مساحة التجزئة (فاصل إعلاني غير إلزامي)، أو عند طلب فاصل إعلاني إجباري.

ثمة قواعد في كيفية تحديد الفواصل الإعلانية غير الإلزامية المثالية، وليس من الأفضل دائمًا إدراج فاصل إعلاني عند نفاد مساحة التخزين. على سبيل المثال، هناك سمات CSS مختلفة، مثل break-before، تؤثر في اختيار موقع الفاصل. لذلك، لتنفيذ قسم مواصفات الفواصل غير الإلزامية أثناء التصميم بشكل صحيح، نحتاج إلى تتبُّع نقاط الإيقاف التي يُحتمل أن تكون جيدة. يعني هذا السجلّ أنّه يمكننا الرجوع واستخدام آخر نقطة توقّف ممكنة تم رصدها، إذا نفدت المساحة عند نقطة خالفنا فيها طلبات تجنب الفواصل (مثل break-before:avoid أو orphans:7). يتم منح كل نقطة توقّف محتملة نتيجة تتراوح بين "إجراء ذلك كحل أخير فقط" و"المكان المثالي للكسر"، مع بعض القيم في ما بينهما. إذا كانت نتيجة موقع الفاصل الإعلاني "مثالية"، فهذا يعني أنه لن يتم انتهاك أي قواعد خرق إذا خالفنا ذلك (وإذا حصلنا على هذه النتيجة عند نفاد المساحة، فلا حاجة إلى البحث عن شيء أفضل). إذا كانت النتيجة "حلف أخير"، فإن نقطة الإيقاف ليست صالحة حتى، ولكن قد نظل نتوقف عند هذا الحد إذا لم نجد أي شيء أفضل، وذلك لتجنب تجاوز حالات التجزئة.

تحدث نقاط التوقف الصالحة بوجه عام بين الأشقاء (مربعات الخط أو الكتل)، وليس، على سبيل المثال، بين عنصر رئيسي وفرعه الأول (تُستثنى من ذلك نقاط التوقف للفئة ج، لكننا لا نحتاج إلى مناقشتها هنا). هناك نقطة توقف صالحة على سبيل المثال قبل شقة تابعة لـ break-before:aتجنُّب، لكنها في محل ما بين "المثالي" و"last-resort".

أثناء التخطيط، نتتبّع أفضل نقطة إيقاف تم رصدها حتى الآن في بنية تُسمى NGEarlyBreak. الفاصل المبكر هو نقطة توقف محتملة قبل عقدة كتلة أو داخلها أو قبل خط (إما خط حاوية كتلة أو خط مرن). وقد نشكل سلسلة أو مسار من كائنات NGEarlyBreak، في حال كانت أفضل نقطة توقف في مكان ما داخل شيء ما مررنا به في وقت سابق عندما نفد الفضاء. وفي ما يلي مثال لذلك:

في هذه الحالة، نفدت مساحة التخزين قبل #second مباشرةً، مع العِلم أنّ هذه المساحة هي "break-before:aتجنب" التي تحصل على نتيجة "تجنُّب الفاصل المخالف" حسب موقع الفواصل الإعلانية. في هذه المرحلة، لدينا سلسلة NGEarlyBreak من "داخل #outer > داخل #middle > داخل #inner > قبل "السطر 3"، مع الكلمة "مثالي"، لذلك نفضّل التقسيم عند هذه النقطة. لذلك نحن بحاجة إلى الرجوع وإعادة تشغيل التخطيط من بداية #outer (وهذه المرة نمرر NGEarlyBreak التي وجدناها)، حتى نتمكن من الفاصل قبل "السطر 3" في #inner. (يتم الفاصل قبل "السطر الثالث"، بحيث تنتهي الأسطر الأربعة المتبقية في المُجزئ التالي، ومن أجل تقدير السمة widows:4).

الخوارزمية مُصمَّمة دائمًا لخرق أفضل نقطة إيقاف ممكنة، على النحو المحدّد في spec، وذلك من خلال تجاهل القواعد بالترتيب الصحيح، في حال لم تتوفّر جميع القواعد بالترتيب الصحيح. تجدر الإشارة إلى أنّه علينا إعادة التنسيق مرة واحدة فقط لكل تدفق تجزئة. وبحلول الوقت الذي وصلنا فيه إلى تمرير التخطيط الثاني، يكون أفضل موقع للفاصل قد تم تمريره بالفعل إلى خوارزميات التخطيط، وهذا هو موقع الفاصل الذي تم اكتشافه في تمرير التخطيط الأول، وتم تقديمه كجزء من إخراج التنسيق في تلك الجولة. في التصريح الثاني للتنسيق، لا نخطط حتى تنفد المساحة - في الواقع، ليس من المتوقع أن تنفد المساحة (هذا كان خطأ في الواقع) لأنه تم توفير مكان رائع للغاية (حسنًا، رائع تمامًا كما كان متوفر) لإدراج فاصل مبكر، لتجنب انتهاك أي قواعد كسرية غير ضرورية. إذًا، ننتقل إلى هذه النقطة وننفصل.

من هذا المنطلق، نضطر أحيانًا إلى انتهاك بعض طلبات تجنب الفواصل الإعلانية إذا كان ذلك يساعد على تجنُّب تجاوز المحتوى المجزأ. مثال:

هنا، نفدت مساحة التخزين قبل #second مباشرةً، ولكنها تحتوي على "استراحة قبل:تجنُّب". يُترجَم ذلك إلى "تجنُّب مخالفة الإرشادات"، تمامًا كما في المثال الأخير. لدينا أيضًا NGEarlyBreak التي تتضمّن عبارة "الأيتام والأرامل التي تنتهك حقوق الطبع والنشر" (داخل #first > قبل "السطر 2")، والتي لا تزال غير مثالية، ولكنّها أفضل من "تجنُّب الفواصل الإعلانية التي تنتهك سياساتنا". لذلك، سنتوقف قبل "السطر 2"، وسنخالف طلب الأيتام / الأرامل. وتتناول المواصفات هذا الأمر في 4.4. الفواصل الإعلانية غير الإجبارية، حيث يتم تحديد القواعد المخالفة التي يتم تجاهلها أولاً إذا لم تتوفر لدينا نقاط توقف كافية لتجنُّب تجاوز عناصر التقسيم.

ملخّص

كان الهدف الوظيفي الرئيسي من مشروع تجزئة كتلة LayoutNG هو توفير واجهة تدعم بنية LayoutNG لكل ما يتيحه المحرك القديم، وبأقل قدر ممكن، بالإضافة إلى إصلاحات الأخطاء. الاستثناء الرئيسي هنا هو توفير دعم أفضل لتجنب الفاصل (break-before:avoid، على سبيل المثال)، لأنّ هذا العنصر جزء أساسي من محرّك التجزئة، لذا يجب أن يكون متوفّرًا من البداية، لأنّ إضافته لاحقًا تعني إعادة كتابة أخرى.

الآن وبعد الانتهاء من تجزئة كتلة LayoutNG، يمكننا بدء العمل على إضافة وظائف جديدة، مثل إتاحة أحجام الصفحات المختلطة عند الطباعة، ومربعات الهوامش @page عند الطباعة، وbox-decoration-break:clone، والمزيد. وكما هو الحال مع LayoutNG بشكل عام، نتوقّع أن ينخفض معدّل الأخطاء وعبء الصيانة للنظام الجديد بشكل ملحوظ بمرور الوقت.

شكرًا على القراءة.

شكر وتقدير