← ब्लॉग

Yoga से कंसोल का लेआउट

Text.js, Yoga को WebAssembly में 1 के पॉइंट स्केल फ़ैक्टर के साथ चलाता है, ताकि हर बॉक्स पूरे सेल पर आए। टेक्स्ट को Intl.Segmenter से हिस्सों में बांटा जाता है और सेल में मापा जाता है।

Adam

Text.js के हर फ़्रेम का लेआउट Yoga बनाता है, जो React Native में इस्तेमाल होने वाला flexbox इंजन है और WebAssembly में कंपाइल किया गया है। बॉक्स और टेक्स्ट एक ही Yoga ट्री में होते हैं। कंसोल कैरेक्टर सेल का एक ग्रिड है, इसलिए इससे निकलने वाली हर पोज़िशन और साइज़ सेल की पूर्ण संख्या में होनी चाहिए।

एक पॉइंट एक सेल है

Yoga अपने नतीजों को पॉइंट स्केल फ़ैक्टर से तय पिक्सेल ग्रिड पर राउंड करता है। Retina स्क्रीन की तरह 2 पर, किनारे आधे पॉइंट पर आ सकते हैं। 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 के बाद जुड़ने वाला ऐक्सेंट एक ही क्लस्टर बनाता है, और ज़ीरो-विथ जॉइनर से जुड़ा इमोजी क्रम भी एक क्लस्टर होता है। फिर हर क्लस्टर को एक चौड़ाई दी जाती है:

टेक्स्टसेलवजह
abc3हर कैरेक्टर के लिए एक सेल
你好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 सेल पर काटा जाता है।