ניתוח מעמיק יותר של NG: פיצול בלוקים של LayoutNG

פיצול החסימה ב-LayoutNG הושלם. במאמר הזה נסביר איך זה עובד ולמה הוא חשוב.

Morten Stenshorne
Morten Stenshorne

שמי מורטן סטונסורן, ואני מהנדס פריסה בצוות העיבוד של Blink ב-Google. אני מעורבת בפיתוח מנועי דפדפנים מאז תחילת שנות ה-2000 ונהניתי מאוד, כמו עזרה בביצוע בדיקת acid2 במנוע Presto (Opera 12 ומטה), והנדסה הפוכה של דפדפנים אחרים כדי לתקן פריסת טבלאות ב-Presto. בנוסף, ביליתי יותר מאותם שנים יותר ממה שאני רוצה לאשר על פיצול של בלוקים, ובמיוחד multicol ב-Presto, ב-WebKit וב-Blink. בשנים האחרונות ב-Google התמקדתי בעיקר בהובלת התמיכה בפיצול בלוקים ל-LayoutNG. הצטרף אלי בסקירה המפורטת הזו בנושא יישום פיצול של בלוקים, מאחר שעשויה להיות לו הפעם האחרונה שאטמיע פיצול של בלוקים. :)

מהי פיצול של בלוקים?

חסימת פיצול מתייחסת לפיצול תיבה ברמת הבלוק של CSS (כמו קטע או פסקה) לכמה מקטעים, כאשר היא לא מתאימה כשלם שלם בתוך מאגר מקטע אחד שנקרא מקטע מקטעים. רכיב המקטע אינו רכיב, אלא מייצג עמודה בפריסה מרובת עמודות, או דף במדיה עם חלוקה לדפים. כדי שתתבצע פיצול, התוכן צריך להיות בתוך הקשר פרגמנטציה. הקשר פרגמנטציה לרוב נקבע על ידי מאגר מרובה עמודות (התוכן יפוצל לעמודות) או בעת הדפסה (התוכן יפוצל לדפים). ייתכן שיהיה צורך לפצל פסקה ארוכה עם שורות רבות למספר מקטעים. כך השורות הראשונות ימוקמו בקטע הראשון והשורות הנותרות ימוקמו במקטעים הבאים.

פסקה של טקסט מחולקת לשתי עמודות.
בדוגמה הזו, פסקה פוצלה לשתי עמודות באמצעות פריסה מרובת עמודות. כל עמודה היא פרגמנטר, שמייצג מקטע מהזרימה המקוטעת.

פרגמנטציה של בלוקים מקבילה לסוג ידוע אחר של פרגמנטציה: פיצול שורות (שנקרא גם "מעבר שורה"). כל אלמנט בתוך שורה שמכיל יותר ממילה אחת (כל צומת טקסט, רכיב <a> כלשהו וכן הלאה) ומאפשר מעברי שורה, יכול להתפצל למספר מקטעים. כל מקטע יוצב בתיבת שורה נפרדת. תיבת שורות היא הפיצול בתוך השורה שמקביל למקטע של עמודות ודפים.

מהי פיצול של בלוקים של LayoutNG?

LayoutNGBlockFragmentation הוא שכתוב של מנוע הפרגמנטציה של LayoutNG. לאחר שנים רבות של עבודה, החלקים הראשונים נשלחו בסופו של דבר ל-Chrome 102 מוקדם יותר השנה. תוקנה בעיה ממושכת שלמעשה לא הייתה אפשרות לתקן במנוע "המדור הקודם" שלנו. במונחים של מבני נתונים, הוא מחליף מבני נתונים מרובים לפני NG בקטעי NG שמיוצגים ישירות בעץ המקטעים.

לדוגמה, אנחנו תומכים עכשיו בערך הימנעות ממאפייני ה-CSS 'break-before' ו-'break-after'. כך מחברים יכולים להימנע מהפסקות מיד אחרי כותרת. בדרך כלל זה לא נראה טוב אם הדבר האחרון שמוצב בדף הוא כותרת בזמן שתוכן הקטע מתחיל בדף הבא. במקום זאת, עדיף למקם קטע קצר לפני הכותרת. עיין באיור שלמטה לקבלת דוגמה.

בדוגמה הראשונה מוצגת כותרת בתחתית הדף, והשנייה מציגה כותרת בחלק העליון של הדף הבא עם התוכן המשויך אליה.

בגרסה 102 של Chrome יש גם תמיכה בגלישה עם פרגמנטציה, כך שתוכן מונוליתי (שאמור להיות בלתי ניתן לשבור) לא יפוצל לעמודות מרובות, ואפקטים של צביעה כמו צלליות והטרנספורמציות יוחלו בצורה נכונה.

חסימת הפיצול ב-LayoutNG הושלמה

נכון למועד כתיבת ההודעה הזו, השלמנו תמיכה מלאה בפיצול בלוקים ב-LayoutNG. פרגמנטציה של הליבה (מאגרים של בלוקים, כולל פריסת קווים, צפים וצימוד לא תקין) שנשלחה בגרסה 102 של Chrome. פיצול של רשתות וגמישות נשלח ב-Chrome 103, ופיצול של טבלאות נשלח ב-Chrome 106. לבסוף, ההדפסה נשלחת ב-Chrome 108. פיצול בלוקים היה התכונה האחרונה שהסתמכה על המנוע מהדור הקודם לביצוע פריסה. המשמעות היא שהחל מגרסה 108 של Chrome, המנוע מהדור הקודם לא ישמש יותר לביצוע פריסה.

בנוסף לפריסה בפועל של התוכן, מבני הנתונים של LayoutNG תומכים בציור ובבדיקת היטים, אבל אנחנו עדיין מסתמכים על כמה מבני נתונים מדור קודם עבור ממשקי API של JavaScript שקוראים מידע על פריסה, כמו offsetLeft ו-offsetTop.

פריסת כל התכונות עם NG תאפשר להטמיע ולשלוח תכונות חדשות שיש להן רק הטמעות של LayoutNG (ולא מקבילות במנוע מדור קודם), כמו שאילתות בקונטיינרים של CSS, מיקום של עוגן, MathML ופריסה מותאמת אישית (Houdini). בתגובה לשאילתות של קונטיינרים, שלחנו את החבילה זמן קצר מראש, עם אזהרה למפתחים על כך שההדפסה עדיין לא נתמכת.

בשנת 2019 שלחנו את החלק הראשון של LayoutNG, שכלל פריסה רגילה של קונטיינרים של בלוקים, פריסה מוטבעת, צפים והצבת מכשירים מחוץ לתהליך. עם זאת, לא הייתה תמיכה בגמישות, במשבצות או בטבלאות, ואין בכלל תמיכה בפיצול בלוקים. נחזור להשתמש במנוע הפריסה מהדור הקודם עבור פלטפורמות גמישות, רשתות, טבלאות וכל מה שכרוך בפיצולי בלוקים. זה היה נכון גם לגבי אלמנטים של בלוקים, מוטבעים, צפים ואלמנטים לא מתקדמים בתוך תוכן מקוטע — כפי שניתן לראות, שדרוג מנוע פריסה מורכב כזה בתוך המערכת הוא ריקוד עדין מאוד.

בנוסף, תאמינו או לא, עד אמצע 2019 רוב הפונקציונליות העיקרית של פריסת פילוח הבלוקים של LayoutNG כבר הוטמעה (מאחורי דגל). אז למה לקח כל כך הרבה זמן לשלוח אותה? התשובה הקצרה היא: פרגמנטציה צריכה להתקיים יחד עם חלקים שונים מדור קודם של המערכת, ולא ניתן להסיר או לשדרג עד שכל יחסי התלות ישודרגו. לקבלת התשובה הארוכה, עיינו בפרטים הבאים.

אינטראקציה עם מנוע מדור קודם

מבני נתונים מדור קודם עדיין אחראים על ממשקי API של JavaScript שקוראים מידע על פריסה, לכן עלינו להשיב נתונים למנוע הקודם באופן שהוא יבין אותו. הפעולות האלה כוללות עדכון תקין של מבני הנתונים מרובי-עמודות מדור קודם, כגון LayoutMultiColumnFlowThread.

זיהוי וטיפול בחלופה למנוע מדור קודם

נאלצנו לחזור למנוע הפריסה מהדור הקודם כשיש בתוכו תוכן שעדיין לא ניתן היה לטפל בו באמצעות פיצול הבלוקים של LayoutNG. נכון למועד פיצול הבלוקים של ליבת המשלוח LayoutNG (אביב 2022), שכלל מוצרים גמישים, רשתות משבצות, שולחנות וכל מה שמודפס. הפעולה הזו הייתה מסובכת במיוחד כי היינו צריכים לזהות את הצורך בחלופה מדור קודם לפני יצירת אובייקטים בעץ הפריסה. לדוגמה, היינו צריכים לזהות לפני שידענו אם יש ישות אב של מאגר מרובה עמודות, ולפני שידענו אילו צומתי DOM יהפכו להקשר עיצוב או לא. זוהי בעיית ביצה ותרנגולת שאין לה פתרון מושלם, אבל כל עוד ההתנהגות הלא תקינה היחידה שלה היא תוצאה חיובית שקרית (חזרה לגרסה קודמת כשאין צורך בפועל), זה בסדר, מכיוון שכל הבאגים בהתנהגות הפריסה הזו הם אלה שכבר קיימים ב-Chromium ולא חדשים.

הליכה בעצים לפני צביעה

עיבוד מראש הוא פעולה שאנחנו מבצעים אחרי הפריסה, אבל לפני הציור. האתגר העיקרי הוא שאנחנו עדיין צריכים ללכת לאורך עץ האובייקטים בפריסה, אבל יש לנו עכשיו שברים של NG — איך מתמודדים עם זה? אנחנו הולכים גם על אובייקט הפריסה וגם עם עצי שבר NG בו-זמנית! התהליך די מורכב, מאחר שהמיפוי בין שני העצים אינו טריוויאלי. בעוד שמבנה עץ אובייקטי הפריסה דומה מאוד לזה של עץ DOM, עץ המקטע הוא פלט של הפריסה, ולא קלט אליו. מלבד השתקפות בפועל של ההשפעה של פרגמנטציה, כולל פיצול מוטבע (קטעי שורות) ופיצולי בלוקים (קטעי דפים או עמודות), לעץ המקטע יש גם קשר ישיר של הורה-צאצא בין בלוק מכיל לצאצאי 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 יש להימנע מביטול: 'לפני:'. כלומר, התוכן רוצה שלא תתרחש הפסקה בין שני הצדדים. מכיוון שהערך של widows הוא 2, אנחנו צריכים לדחוף 2 שורות של #firstchild לעמודה השנייה, כדי לפעול בהתאם לכל הבקשות להימנעות מההפסקות. Chromium הוא מנוע הדפדפן הראשון שתומך באופן מלא בשילוב התכונות הזה.

איך עובדת פיצול NG

מנוע הפריסה NG בדרך כלל פורס את המסמך על ידי מעבר עומק העץ של תיבת ה-CSS קודם כל. לאחר פריסת כל הצאצאים של הצומת, ניתן להשלים את הפריסה של הצומת הזה על ידי יצירת NGPhysicalFragment וחזרה לאלגוריתם פריסת ההורה. האלגוריתם הזה מוסיף את המקטע לרשימת מקטעי הצאצא שלו, ובסיום היצירה של כל הצאצאים, הוא יוצר לעצמו מקטע שכולל את כל שברי הצאצא שלו. בשיטה הזו הוא יוצר עץ מקטעים עבור המסמך כולו. עם זאת, זוהי הדגמה פשוטה מדי: לדוגמה, רכיבים שאינם נמצאים בתהליך העבודה יצטרכו להופיע בבועות במקום שבו הם קיימים בעץ ה-DOM אל הבלוק המכיל שלהם לפני שניתן יהיה לפרוס אותם. לשם הפשטות, אני מתעלם מהפרטים המתקדמים שמופיעים כאן.

בנוסף לתיבת ה-CSS עצמה, LayoutNG מספקת שטח אילוצים לאלגוריתם פריסה. כך האלגוריתם מספק מידע כמו המקום הזמין לפריסה, האם נקבע הקשר חדש לעיצוב והתוצאות של כיווץ שוליים הביניים מהתוכן הקודם. מרחב האילוצים גם יודע את גודל הבלוקים הפרושים של המקטע ואת הבלוק הנוכחי שמתקזז אליו. מסמן את המיקום שבו צריך להפסיק לעבוד.

במקרה של פיצול בלוקים, פריסת הצאצאים צריכה להסתיים בהפסקה. הסיבות לפריצה יכולות להיות שנגמר המקום בדף או בעמודה, או הפסקות מאולצות. לאחר מכן, אנחנו מייצרים מקטעים עבור הצמתים שבהם ביקרנו, וחוזרים עד לשורש של הקשר הפיצול (המאגר מרובה-העמודות, או, במקרה של הדפסה, הרמה הבסיסית של המסמך). לאחר מכן, בשורש ההקשר של הפיצול, אנחנו מתכוננים למקטע חדש, ויורדים שוב לעץ, וממשיכים מהמקום שבו עצרנו לפני ההפסקה.

מבנה הנתונים החיוני למתן אמצעים להמשך הפריסה לאחר הפסקה נקרא NGBlockBreakToken. היא מכילה את כל המידע הדרוש כדי להמשיך את הפריסה בצורה נכונה במקטע הבא. NGBlockBreakToken משויך לצומת, ויוצר עץ NGBlockBreakToken, כך שכל צומת שצריך להפעיל מחדש מיוצג. NGBlockBreakToken מצורף ל-NGPhysicalBoxFragment שנוצר עבור צמתים שנקטעו פנימה. אסימוני ההפסקה מועברים להורים, וכך יוצרים עץ של אסימוני ההפסקה. אם אנחנו צריכים לפרוץ לפני צומת (ולא בתוכו), לא יופק מקטע. עם זאת, הצומת ההורה עדיין צריך ליצור אסימון עצירה מסוג 'break-before' עבור הצומת, כדי שנוכל להתחיל לבצע את הפריסה כשנגיע לאותו מיקום בעץ הצמתים בקטע הבא.

המערכת מוסיפה מעברי שורה כשנגמר הרווח המקטע (מעבר לא מאולץ) או כשנשלחת בקשה להפסקה מאולצת.

יש כללים במפרט להפסקות לא מאולצות אופטימליות. הוספת הפסקה בדיוק במקום שבו נגמר המקום היא לא תמיד הפעולה הנכונה. לדוגמה, יש מאפייני CSS שונים כמו break-before שמשפיעים על הבחירה של מיקום ההפסקה. לכן, במהלך פריסה, כדי להטמיע בצורה נכונה את קטע המפרט של הפסקות לא מאולצות, אנחנו צריכים לעקוב אחרי נקודות עצירה (breakpoint) שעשויות להיות טובות. המשמעות של רשומת זו היא שאנחנו יכולים לחזור ולהשתמש בנקודת העצירה האחרונה הטובה ביותר שאפשר למצוא, אם נגמר המקום בנקודה שבה היינו מפרים בקשות מניעה (לדוגמה, break-before:avoid או orphans:7). כל נקודת עצירה אפשרית תקבל ציון, שנע בין 'עשה זאת רק כמוצא אחרון' ועד 'מקום מושלם להפסקה', עם ערכים ביניהם. אם מיקום הפסקה מדורג כ"מושלם", המשמעות היא שלא תהיה הפרה של כללי הפרה אם נשבר זאת (ואם נקבל את הציון הזה בדיוק בנקודה שבה נגמר לנו המקום, אין צורך להביט לאחור ולבדוק משהו טוב יותר). אם התוצאה היא 'האיתור האחרון', נקודת העצירה היא אפילו לא חוקית, אבל עדיין ייתכן שנקטע שם אם לא נמצא משהו טוב יותר כדי למנוע גלישת מקטעים.

בדרך כלל, נקודות עצירה תקינות מתרחשות בין אחים ואחיות (תיבות שורה או בלוקים) ולא, למשל, בין הורה לבין הצאצא הראשון שלו (נקודות עצירה ברמה C הן חריגות, אבל אנחנו לא צריכים להסביר אותן כאן). יש נקודת עצירה (breakpoint) תקינה, למשל, לפני בלוק אח עם break-before:aהימנעות, אבל הוא נמצא בטווח שבין 'מושלם' ל-'מוצא אחרון'.

במהלך הפריסה, אנחנו עוקבים אחרי נקודות העצירה הטובות ביותר שנמצאו עד כה במבנה שנקרא NGEarlyBreak. הפסקה מוקדמת היא נקודת עצירה (breakpoint) אפשרית לפני או בתוך צומת של בלוק, או לפני קו (קו מאגר בלוקים או קו גמיש). אנחנו עשויים ליצור שרשרת או נתיב של אובייקטים של NGEarlyBreak, למקרה שנקודת העצירה הטובה ביותר נמצאת עמוק בתוך משהו שעברנו עליו מוקדם יותר כשנגמר המקום. לדוגמה:

במקרה הזה, נגמר לנו המקום ממש לפני #second, אבל הוא כולל את הכיתוב "break-before:aenable", שמקבל את ציון המיקום של ההפסקה "הימנעות מהפרה של המדיניות". בשלב זה יש לנו שרשרת NGEarlyBreak מסוג "בתוך #outer > בתוך #middle > בתוך #inner > לפני "שורה 3", עם "מושלם", כך שנעדיף לעבור שם. לכן אנחנו צריכים להחזיר ולהפעיל מחדש את הפריסה מתחילת ה- #outer (והפעם להעביר את NGEarlyBreak שמצאנו), כדי שנוכל לעבור לפני "שורה 3" ב- #inner. (אנחנו עוברים לפני "שורה 3", כדי ש-4 השורות הנותרות יסתיימו בקטע הבא, וכדי לכבד את widows:4).

האלגוריתם תוכנן תמיד לשבור בנקודת העצירה הטובה ביותר (כפי שמוגדר בspec) על ידי שחרור הכללים בסדר הנכון, אם לא ניתן לעמוד בכולם. הערה: צריך לבצע פריסה מחדש פעם אחת לכל היותר בכל תהליך פיצול. עד שנעבור לפריסה השנייה, המיקום הטוב ביותר להפסקה כבר הועבר לאלגוריתמים של הפריסה. זהו מיקום ההפסקה שהתגלה במעבר הפריסה הראשון, וסופק כחלק מפלט הפריסה בסבב הזה. בתוכנית הפריסה השנייה, אנחנו לא פורסים את הפריסה עד שנגמר המקום — למעשה לא צפוי שייגמר המקום (זו למעשה טעות), כי קיבלנו מקום מתוק (כלומר, מתוק ככל שיהיה) שבו ניתן להוסיף הפסקה מוקדמת כדי למנוע הפרת כללים שלא לצורך. אז אנחנו פשוט פורשים בנקודה הזו, ונשברים.

בהערה הזו, לפעמים אנחנו צריכים להפר חלק מהבקשות להימנעות מההפסקות, אם הדבר עוזר למנוע גלישת של מקטעים. דוגמה:

כאן, נגמר לנו המקום ממש לפני #second, אבל יש לו "break-before:aהימנעות". אנחנו מתרגמים אותם ל"מניעת הפסקות תוך הפרה", בדיוק כמו בדוגמה הקודמת. יש לנו גם NGEarlyBreak עם "יתומים ואלמנים מפרים" (בתוך #first > לפני "שורה 2"), שעדיין לא מושלם, אבל עדיף על 'מניעת הפסקה מהפרה'. לכן, נשתמש בהנחיה לפני "שורה 2", ונפרו את בקשת היתומים / האלמנים. המפרט מתייחס לכך בסעיף 4.4. הפסקות לא מאולצות, כאשר הן מגדירות מאילו כללי הפרה המערכת מתעלמת קודם אם אין לנו מספיק נקודות עצירה (breakpoint) כדי למנוע גלישת של מקטעים.

סיכום

המטרה הפונקציונלית העיקרית בפרויקט פיצול הבלוקים של LayoutNG הייתה לספק הטמעה שתומכת בארכיטקטורת LayoutNG של כל מה שהמנוע מהדור הקודם תומך בו, ועוד כמה שפחות, מלבד תיקוני באגים. היוצא מן הכלל העיקרי במקרה זה הוא תמיכה טובה יותר במניעת הפסקות (break-before:avoid, למשל), מפני שזהו חלק עיקרי במנוע הפיצול, לכן הוא היה צריך להופיע בו מלכתחילה, שכן הוספה שלו מאוחר יותר תגרום לשכתוב נוסף.

עכשיו, לאחר שסיימנו את פיצול הבלוקים של LayoutNG, נוכל להתחיל לעבוד על הוספת פונקציונליות חדשה. למשל, תמיכה בגדלים של דפים מעורבים במהלך הדפסה, @page תיבות שוליים במהלך הדפסה, box-decoration-break:clone ועוד. כמו במקרה של LayoutNG באופן כללי, אנחנו צופים שקצב הבאגים ועומס התחזוקה של המערכת החדשה יצטמצמו באופן משמעותי עם הזמן.

תודה על שקראת מידע זה!

אישורים