تخطيط وحدة التحكم باستخدام Yoga
يشغّل Text.js محرك Yoga في WebAssembly بمعامل مقياس نقاط قدره 1، لذا يستقر كل صندوق على خلية كاملة. يُجزّأ النص باستخدام Intl.Segmenter ويُقاس بالخلايا.
يتولى Yoga، محرك flexbox المستخدم في React Native والمترجم إلى WebAssembly، تخطيط كل إطار في 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 خلية.