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

Morten Stenshorne
Morten Stenshorne

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

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

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

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

تجزئة وحدات LayoutNG

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

على سبيل المثال، نتيح الآن استخدام القيمة avoid لخصائص CSS break-before وbreak-after، ما يسمح للمؤلفين بتجنُّب الفواصل مباشرةً بعد العنوان. غالبًا ما يبدو الأمر غير ملائم عندما يكون العنصر الأخير في الصفحة هو عنوان، بينما يبدأ محتوى القسم في الصفحة التالية. من الأفضل إضافة فاصل قبل العنوان.

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

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

اكتملت الآن عملية تقسيم العناصر في LayoutNG

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

اعتبارًا من الإصدار 108 من Chrome، لم يعُد يتم استخدام المحرّك القديم لتنفيذ التنسيق.

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

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

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

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

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

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

رصد ومعالجة المحتوى الاحتياطي في المحرّك القديم

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

جولة في الأشجار قبل الطلاء

نُجري عملية ما قبل الطلاء بعد التنسيق، ولكن قبل الطلاء. يكمن التحدي الرئيسي في أنّنا ما زلنا بحاجة إلى التنقّل في شجرة عناصر التنسيق، ولكن لدينا الآن أجزاء NG، فكيف نتعامل مع ذلك؟ ننتقل في الوقت نفسه إلى كلّ من عنصر التنسيق وأشجار المقاطع في تنسيق NG. وهذا الأمر معقّد للغاية، لأنّ الربط بين الشجرتَين ليس بسيطًا.

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

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

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

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

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

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

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

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

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

في ما يلي مثال على استخدام text-shadow:

لا يعالج المحرّك القديم هذه المشكلة بشكلٍ جيد:

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

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

من المفترض أن يظهر الرمز على النحو التالي:

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

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

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

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

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

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

ALT_TEXT_HERE

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

النص مُقسَّم إلى عمودَين.

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

آلية عمل "التقسيم الجديد"

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

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

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

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

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

هناك قواعد في المواصفات لتحديد الفواصل غير المُجبرة المثلى، ولا يكون إدراج فاصل في المكان الذي تنفد فيه المساحة دائمًا هو الخيار الصحيح. على سبيل المثال، هناك خصائص مختلفة في CSS مثل break-before تؤثر في اختيار موضع الفاصل.

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

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

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

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

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

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

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

الخاتمة

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

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

الشكر والتقدير

  • Una Kravets على "لقطة الشاشة المصنوعة يدويًا" الرائعة.
  • كريس هارلسون لمراجعة النصوص وتقديم الملاحظات والاقتراحات
  • Philip Jägenstedt للحصول على الملاحظات والاقتراحات
  • راشيل أندرو لتعديل أول مثال على رسم بياني متعدّد الأعمدة