← Blog

Testo da destra a sinistra nella console

Text.js implementa l’algoritmo bidirezionale Unicode e supera tutti i 770.241 casi di BidiTest.txt. Ebraico e arabo sono visualizzati in ordine visivo con shaping arabo, allineati alla griglia di celle.

Adam

Text.js ora visualizza l’ebraico, l’arabo, il persiano e altre scritture da destra a sinistra nell’ordine corretto, con le lettere arabe unite, sulla stessa griglia di celle usata per tutto il resto.

import { Box, Text } from "@textjs/react";export default function Page() {  return (    <Box flexDirection="column" padding={1}>      <Text>שלום עולם! זהו טקסט בעברית.</Text>      <Text>مرحبا بالعالم! هذا نص عربي.</Text>      <Text>Order #1234 (<Text color="accent">שלום</Text>) total: 25.50 ₪</Text>      <Text>user <Text dir="auto" color="success">שלום!</Text> replied 3 times</Text>    </Box>  );}

Ogni paragrafo ricava la propria direzione di base dal primo carattere forte, come fa dir="auto" in HTML, e i paragrafi da destra a sinistra si allineano a destra. Su un <Text> annidato, dir isola lo span come fa <bdi>, così un nome utente dalla direzione sconosciuta non può riordinare il testo circostante.

L’algoritmo

Il supporto per la scrittura da destra a sinistra è un’implementazione dello Unicode Bidirectional Algorithm (UAX #9, Unicode 18.0), scritta da zero in @textjs/core. Risolve i livelli di incorporamento per ogni paragrafo, inclusi incorporamenti espliciti e isolamenti, tipi deboli e neutri e coppie di parentesi. Poi ogni riga ottenuta dall’andata a capo viene riordinata separatamente e i caratteri speculari si scambiano ai livelli da destra a sinistra, così (abc) resta (abc) all’interno dell’ebraico.

Unicode pubblica due file di conformità per l’algoritmo. Entrambi sono inclusi nel repository, compressi con gzip, e la suite di test esegue ogni caso:

  • BidiTest.txt elenca gli input come sequenze di classi bidi, ciascuna con le direzioni del paragrafo a cui si applica. Text.js supera tutti i 770.241 casi.
  • BidiCharacterTest.txt elenca stringhe reali con i livelli e l’ordine attesi. Text.js li supera tutti e 91.707.
for (const [bit, dir] of [[1, -1], [2, 0], [4, 1]] as const) {  if ((bitset & bit) === 0) continue;  cases++;  const r = run(types, null, dir);  const gotLevels = fmtLevels(r.levels);  const gotOrder = r.order.join(" ");  if (gotLevels !== levels || gotOrder !== reorder) {    if (failures.length < 20) failures.push(`${input.trim()} [dir ${dir}]: levels ${gotLevels} (want ${levels}), order ${gotOrder} (want ${reorder})`);    else failures.push("");  }}

Dall’ordine logico alle celle

Una riga di testo da destra a sinistra attraversa quattro passaggi prima di raggiungere la console:

  1. Andare a capo in ordine logico. Le righe si interrompono dove si interromperebbero per il testo da sinistra a destra, in base alla larghezza delle celle.
  2. Riordinare ogni riga. I livelli risolti determinano l’ordine di visualizzazione dei grafemi della riga.
  3. Applicare le forme arabe. Le lettere assumono le forme contestuali (isolata, iniziale, mediana, finale) dai blocchi Arabic Presentation Forms. Una lettera occupa una cella e le forme unite restano tali anche con un cursore o un cambio di colore nel mezzo di una parola.
  4. Fissare l’ordine. Le celle ora contengono i glifi nell’ordine di visualizzazione.

L’ultimo passaggio esiste perché DevTools applicherebbe bidi al testo una seconda volta, invertendolo di nuovo. Uno stile %c non può disattivare questo comportamento, perché il filtro di DevTools elimina direction e unicode-bidi. Il driver quindi racchiude ogni span che contiene caratteri da destra a sinistra in LRO e PDF (U+202D e U+202C), forzando la visualizzazione da sinistra a destra dell’esatto contenuto delle celle.

Uno scostamento di mezza cella

Né SF Mono né Menlo contengono glifi ebraici o arabi, quindi il browser usa un font di fallback per ogni carattere. Su macOS è Courier New, anch’esso a spaziatura fissa, con 7,2012 px per glifo a 12 px contro i 7,2246 px di Menlo. È più stretto dello 0,3%, quanto basta per spostare un’intera riga di ebraico di circa mezza cella.

CssConsoleDriver misura ogni span da destra a sinistra con un canvas nel font della console, che usa lo stesso fallback dei font di Blink usato da DevTools, e aggiunge allo span uno spazio pari alla differenza esatta in LayoutUnits (1/64 px). Ogni span finisce per avere la stessa larghezza delle celle della griglia che occupa. Uno span viene ristretto al massimo di mezza cella, così un font di fallback molto più largo deborda invece di sovrapporsi allo span successivo.

bun bench/devtools/bidi-shot.ts misura il risultato nel frontend reale di DevTools, su un frame di 64×21 con righe in ebraico, in arabo, miste e con emoji nel testo ebraico:

FrameScostamento del bordo destro tra le righe
Celle stampate senza fissare l’ordine14,5 px
Ordine fissato, senza adattamento della larghezza2,22 px
Driver distribuito0,156 px
Controllo: ogni glifo RTL sostituito da x1,67 px

Il controllo è lo stesso frame con testo latino. Il driver distribuito si allinea meglio del controllo perché le emoji all’interno degli span da destra a sinistra vengono adattate insieme a essi.

Il testo da sinistra a destra resta invariato

Il testo senza caratteri da destra a sinistra salta tutti questi passaggi. Il controllo avviene durante la segmentazione dei grafemi, con un confronto per grafema, e il testo interamente da sinistra a destra viene disegnato attraverso lo stesso percorso di prima, quindi i suoi frame nella console sono identici byte per byte.

Il testo da destra a sinistra è incluso in Text.js 1.0. Internazionalizzazione tratta input, modifica, terminali e API.