← Blog

Yoga ile konsol layout’u

Text.js, Yoga’yı WebAssembly içinde 1 nokta ölçek faktörüyle çalıştırır; böylece her kutu tam bir hücreye oturur. Metin, Intl.Segmenter ile bölümlenir ve hücre cinsinden ölçülür.

Adam

Her Text.js karesinin layout’unu, React Native’in kullandığı ve WebAssembly’ye derlenen flexbox motoru Yoga oluşturur. Kutular ve metin aynı Yoga ağacını paylaşır. Konsol, karakter hücrelerinden oluşan bir ızgaradır; bu nedenle üretilen her konum ve boyutun tam sayıda hücreye karşılık gelmesi gerekir.

Bir punto bir hücre

Yoga, sonuçlarını punto ölçek faktörünün tanımladığı piksel ızgarasına yuvarlar. Faktör 2 olduğunda, Retina ekranlarda olduğu gibi, kenarlar yarım puntolara denk gelebilir. Text.js, Yoga’yı bir kez yükler ve faktörü 1 olarak ayarlar:

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;}

Faktör 1 olduğunda Yoga her kenarı tam birime yuvarlar ve Text.js bir birimi bir hücre olarak ele alır. Yoga, her kenarın mutlak konumunu yuvarlar ve boyutları yuvarlanmış kenarlardan türetir; böylece komşu kutular bir kenarı paylaşır. 81 sütunluk bir satırı iki width="50%" kutusuna böldüğünüzde biri 40, diğeri 41 sütun kaplar; arada boşluk veya örtüşme olmaz.

Text.js, CSS varsayılanlarını kendisi uygular: tarayıcıda olduğu gibi flexDirection değeri row, flexShrink değeri ise 1 olur.

Metin ölçümü

Metin düğümleri, ölçüm fonksiyonu olan Yoga yapraklarıdır. Yoga bu fonksiyonu kullanılabilir genişlikle çağırır ve Text.js metnin boyutunu hücre cinsinden döndürür:

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 };}

Sığan metin doğal boyutunda döndürülür. Sığmayan metin kullanılabilir genişlikte satırlara bölünür ve yüksekliği satır sayısı olur. Düğüm son satır bölme sonucunu saklar; böylece aynı genişlik tekrar ölçüldüğünde metin yeniden satırlara bölünmez.

Grafem kümelerinin ölçümü

Bir dizgenin length değeri UTF-16 kod birimlerini sayar; bu da kaç hücre doldurduğu hakkında pek bilgi vermez. Metin önce Intl.Segmenter ile grafem kümelerine ayrılır; böylece ardından birleştirici aksan gelen e tek bir küme oluşturur, sıfır genişlikli birleştiricilerle bağlanmış bir emoji dizisi de öyle. Ardından her kümeye bir genişlik atanır:

MetinHücreNeden
abc3Karakter başına bir hücre
你好4Doğu Asya geniş karakterleri iki hücre kaplar
🚀2Emoji gösterimi iki hücre kaplar
é (e + U+0301)1Birleştirici işaretler hücre kaplamaz
─│┌▼←⠋6Kutu çizimi, geometrik şekiller, oklar ve braille bir hücreye sabitlenir

Kenarlıklar, ▼ işareti, oklar ve yükleme göstergesi kareleri her zaman bir hücre olarak ölçülür; böylece kutu kenarlığı içeriğin hizasını bozmaz.

Yazdırılabilir ASCII için bölümleme atlanır; diğer genişlikler kod noktası ve küme başına önbelleğe alınır.

Satırlara bölme

<Text> bir wrap modu alır:

  • wrap (varsayılan), boşluklarda ve sözcük satırdan geniş olduğunda sözcük içinde satırı böler.
  • nowrap tek satırı korur ve kutunun satırı kırpmasına izin verir.
  • truncate-end, truncate-middle ve truncate-start tek satırı korur ve sığmayan kısmı … ile değiştirir.

Kısaltma da hücreleri sayar; bu nedenle Çince bir metin satırı karakter sınırında kesilir ve üç nokta tam olarak bir hücre kaplar.

Uygulama

Burada 30 sütunluk bir kenar çubuğunun yanında kalan genişliği kaplayan bir panel bulunur:

<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 sütunluk bir konsolda kenar çubuğu, kenarlığı dahil 30 sütun kaplar; konuşma paneli ise kalan 70 sütunu kaplar. Kenarlık her iki tarafta birer hücre kapladığından oturum başlığı 28 hücrede kısaltılır.