סידור המסוף עם Yoga
Text.js מריץ את Yoga ב-WebAssembly עם מקדם קנה מידה לנקודות של 1, כך שכל תיבה ממוקמת על תא שלם. הטקסט מחולק למקטעים עם Intl.Segmenter ונמדד בתאים.
Yoga, מנוע ה-flexbox המשמש את React Native ומקומפל ל-WebAssembly, קובע את ה-layout של כל פריים של Text.js. תיבות וטקסט חולקים עץ Yoga אחד. המסוף הוא רשת של תאי תווים, ולכן כל מיקום וגודל שמתקבלים ממנו חייבים להיות מספר שלם של תאים.
נקודה אחת היא תא אחד
Yoga מעגל את התוצאות שלו לרשת פיקסלים שמוגדרת לפי מקדם קנה המידה של הנקודות. כשהמקדם הוא 2, כמו במסך Retina, קצוות יכולים ליפול על חצאי נקודות. Text.js טוען את Yoga פעם אחת ומגדיר את המקדם ל-1:
export function initYoga(): Promise<Yoga> { if (yoga !== null) return Promise.resolve(yoga); if (yogaPromise === null) { yogaPromise = loadYoga().then((instance) => { yoga = instance; const config = instance.Config.create(); config.setPointScaleFactor(1); config.setUseWebDefaults(false); yogaConfig = config; return instance; }); yogaPromise.catch(() => { yogaPromise = null; }); } return yogaPromise;}עם מקדם של 1, Yoga מעגל כל קצה ליחידה שלמה, ו-Text.js מתייחס ליחידה אחת כתא אחד. Yoga מעגל את המיקום המוחלט של כל קצה וגוזר את הגדלים מהקצוות המעוגלים, כך שתיבות סמוכות חולקות קצה. כשמפצלים שורה של 81 עמודות לשתי תיבות עם width="50%", אחת מקבלת 40 עמודות והשנייה 41, ללא רווח וללא חפיפה.
Text.js מחיל בעצמו את ברירות המחדל של CSS: הערך של flexDirection הוא row והערך של flexShrink הוא 1, כמו בדפדפן.
מדידת טקסט
צומתי טקסט הם עלים בעץ Yoga עם פונקציית מדידה. Yoga קורא לה עם הרוחב הזמין, ו-Text.js מחזיר את גודל הטקסט בתאים:
private measure(width: number, widthMode: MeasureMode, _height: number, _heightMode: MeasureMode): { width: number; height: number } { const natural = this.naturalSize(); if (widthMode === MeasureMode.Undefined || Number.isNaN(width)) return natural; const avail = Math.max(0, Math.floor(width)); if (natural.width <= avail) { return { width: widthMode === MeasureMode.Exactly ? avail : natural.width, height: natural.height }; } if ((this.props.wrap ?? "wrap") !== "wrap") return { width: avail, height: natural.height }; const lines = this.getLines(avail); let maxLine = 0; for (const l of lines) if (l.width > maxLine) maxLine = l.width; return { width: widthMode === MeasureMode.Exactly ? avail : Math.min(avail, maxLine), height: lines.length };}טקסט שנכנס ברוחב הזמין מוחזר בגודלו הטבעי. טקסט שלא נכנס נשבר לשורות לפי הרוחב הזמין, וגובהו הוא מספר השורות. הצומת שומר את שבירת השורות האחרונה, כך שמדידה חוזרת של אותו רוחב לא שוברת את הטקסט לשורות מחדש.
מדידת אשכולות גרפמות
המאפיין length של מחרוזת סופר יחידות קוד של UTF-16, שלא מעידות הרבה על מספר התאים שהיא ממלאת. תחילה הטקסט מחולק לאשכולות גרפמות באמצעות Intl.Segmenter, כך ש-e שאחריו סימן הטעמה משלב הוא אשכול אחד, וכך גם רצף אמוג'י המחובר בתווי חיבור ברוחב אפס. לאחר מכן מוקצה רוחב לכל אשכול:
| טקסט | תאים | סיבה |
|---|---|---|
abc | 3 | תא אחד לכל תו |
你好 | 4 | תווים רחבים ממזרח אסיה תופסים שני תאים |
🚀 | 2 | תצוגת אמוג'י תופסת שני תאים |
é (e + U+0301) | 1 | סימנים משלבים לא תופסים תאים |
─│┌▼←⠋ | 6 | תווי ציור מסגרות, צורות גאומטריות, חיצים וברייל מקובעים לתא אחד |
מסגרות, הסימן ▼, חיצים ופריימים של מחוון טעינה נמדדים תמיד כתא אחד, כך שמסגרת של תיבה לעולם לא מוציאה את תוכנה מהיישור.
תווי ASCII הניתנים להדפסה מדלגים על החלוקה לאשכולות, ורוחבים אחרים נשמרים במטמון לכל נקודת קוד ולכל אשכול.
שבירת שורות
<Text> מקבל מצב wrap:
wrap(ברירת המחדל) שובר שורות ברווחים, ובתוך מילה כשהמילה רחבה מהשורה.nowrapשומר על שורה אחת ומאפשר לתיבה לחתוך אותה.truncate-end,truncate-middleו-truncate-startשומרים על שורה אחת ומחליפים את מה שלא נכנס ב-….
גם קיצור טקסט סופר תאים, כך ששורת טקסט בסינית נחתכת בגבול של תו, ושלוש הנקודות תופסות תא אחד בדיוק.
בפועל
כאן סרגל צד ברוחב 30 עמודות נמצא לצד חלונית שתופסת את הרוחב הנותר:
<Box flexDirection="row" width="100%" height="100%"> <Box width={30} flexDirection="column" border="rounded" title="Sessions" overflow="hidden"> <Text wrap="truncate-end">session-03 · refactor the renderer pipeline</Text> </Box> <Box flexGrow={1} flexDirection="column" border="single" title="Conversation" paddingX={1}> <Text>Message 0: 你好 🚀</Text> </Box></Box>במסוף ברוחב 100 עמודות, סרגל הצד תופס 30 עמודות כולל המסגרת שלו, וחלונית השיחה תופסת את 70 העמודות הנותרות. מסגרת תופסת תא אחד בכל צד, ולכן כותרת הסשן מקוצרת ב-28 תאים.