← وبلاگ

متن راست‌به‌چپ در کنسول

⁦Text.js⁩ الگوریتم دوجهتهٔ Unicode را پیاده‌سازی می‌کند و تمام 770,241 مورد آزمون BidiTest.txt را با موفقیت می‌گذراند. متن عبری و عربی به ترتیب بصری، با شکل‌دهی حروف عربی و هم‌تراز با شبکهٔ سلول‌ها رندر می‌شود.

Adam

⁦Text.js⁩ اکنون عبری، عربی، فارسی و دیگر خط‌های راست‌به‌چپ را با ترتیب درست و حروف عربیِ متصل، روی همان شبکهٔ سلولیِ سایر محتوا رندر می‌کند.

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

هر پاراگراف جهت پایهٔ خود را از نخستین نویسهٔ قوی می‌گیرد، همان‌طور که dir="auto" در HTML عمل می‌کند، و پاراگراف‌های راست‌به‌چپ راست‌چین می‌شوند. در یک <Text> تودرتو، ویژگی dir مانند <bdi> span را ایزوله می‌کند تا نام کاربری با جهت نامشخص نتواند ترتیب متن اطرافش را تغییر دهد.

الگوریتم

پشتیبانی راست‌به‌چپ، پیاده‌سازی الگوریتم دوجهتهٔ Unicode ‏(⁦UAX #9⁩، ⁦Unicode 18.0⁩) است که از ابتدا در @textjs/core نوشته شده است. این پیاده‌سازی سطوح تعبیه را برای هر پاراگراف تعیین می‌کند، از جمله تعبیه‌ها و ایزوله‌های صریح، انواع ضعیف و خنثی و پرانتزهای جفت‌شده. سپس ترتیب هر خطِ شکسته‌شده جداگانه تغییر می‌کند و نویسه‌های قرینه در سطوح راست‌به‌چپ جایگزین می‌شوند تا (abc) در متن عبری همان (abc) بماند.

⁦Unicode⁩ دو فایل آزمون انطباق برای این الگوریتم منتشر می‌کند. هر دو به‌صورت فشرده با gzip در مخزن ثبت شده‌اند و مجموعهٔ آزمون همهٔ موارد را اجرا می‌کند:

  • فایل BidiTest.txt ورودی‌ها را به‌صورت دنباله‌هایی از کلاس‌های دوجهته فهرست می‌کند و برای هرکدام جهت‌های پاراگراف قابل اعمال را مشخص می‌کند. Text.js هر 770,241 مورد را با موفقیت می‌گذراند.
  • فایل BidiCharacterTest.txt رشته‌های واقعی را همراه با سطوح و ترتیب مورد انتظار فهرست می‌کند. Text.js هر 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("");  }}

از ترتیب منطقی تا سلول‌ها

یک خط متن راست‌به‌چپ پیش از رسیدن به کنسول چهار مرحله را طی می‌کند:

  1. شکستن خطوط در ترتیب منطقی. خطوط بر اساس عرض سلول، در همان جاهایی شکسته می‌شوند که متن چپ‌به‌راست شکسته می‌شود.
  2. تغییر ترتیب هر خط. سطوح تعیین‌شده ترتیب نمایش گرافیم‌های خط را مشخص می‌کنند.
  3. شکل‌دهی حروف عربی. حروف شکل متناسب با جایگاهشان (منفصل، ابتدایی، میانی، پایانی) را از بلوک‌های ⁦Arabic Presentation Forms⁩ می‌گیرند. هر حرف یک سلول است و شکل‌های متصل با قرار گرفتن مکان‌نما یا تغییر رنگ در میانهٔ کلمه حفظ می‌شوند.
  4. تثبیت ترتیب. اکنون سلول‌ها گلیف‌ها را به ترتیب نمایش نگه می‌دارند.

مرحلهٔ آخر به این دلیل وجود دارد که DevTools الگوریتم دوجهته را بار دوم روی متن اجرا می‌کند و دوباره ترتیب آن را معکوس می‌کند. سبک %c نمی‌تواند این رفتار را خاموش کند، زیرا پاک‌ساز DevTools ویژگی‌های direction و unicode-bidi را حذف می‌کند. بنابراین درایور هر span حاوی نویسه‌های راست‌به‌چپ را در LRO و PDF (⁦U+202D⁩ و ⁦U+202C⁩) قرار می‌دهد تا دقیقاً محتوای سلول‌ها به‌اجبار چپ‌به‌راست نمایش داده شود.

انحراف به‌اندازهٔ نیم سلول

نه ⁦SF Mono⁩ و نه Menlo گلیف عبری یا عربی ندارند، بنابراین مرورگر برای هر نویسه از قلم جایگزین استفاده می‌کند. در macOS این قلم ⁦Courier New⁩ است که آن هم تک‌عرض است و در اندازهٔ 12 px، عرض هر گلیف آن 7.2012 px در برابر 7.2246 px در Menlo است. عرض آن ⁦0.3%⁩ کمتر است که برای انحراف یک ردیف کامل عبری به‌اندازهٔ حدود نیم سلول کافی است.

درایور CssConsoleDriver هر span راست‌به‌چپ را با یک بوم و قلم کنسول اندازه می‌گیرد که از همان سازوکار جایگزینی قلم Blink در DevTools استفاده می‌کند، و به‌اندازهٔ دقیق اختلاف بر حسب LayoutUnits (1/64 px) به span فاصله اضافه می‌کند. در نهایت عرض هر span برابر با سلول‌های شبکه‌ای می‌شود که می‌پوشاند. هر span حداکثر نیم سلول جمع می‌شود، بنابراین قلم جایگزینی که بسیار عریض‌تر باشد، به‌جای هم‌پوشانی با span بعدی سرریز می‌کند.

دستور bun bench/devtools/bidi-shot.ts نتیجه را در رابط واقعی DevTools، روی یک فریم ⁦64×21⁩ شامل خطوط عبری، عربی، ترکیبی و ایموجی در متن عبری اندازه می‌گیرد:

فریمانحراف لبهٔ راست میان ردیف‌ها
سلول‌های ثبت‌شده بدون تثبیت ترتیب14.5 px
ترتیب تثبیت‌شده، بدون تنظیم عرض2.22 px
درایور منتشرشده0.156 px
نمونهٔ کنترل: جایگزینی هر گلیف راست‌به‌چپ با x1.67 px

نمونهٔ کنترل همان فریم با متن لاتین است. تراز درایور منتشرشده از نمونهٔ کنترل بهتر است، زیرا ایموجی‌های داخل spanهای راست‌به‌چپ همراه با آن‌ها تنظیم عرض می‌شوند.

بدون تغییر در متن چپ‌به‌راست

متن فاقد نویسه‌های راست‌به‌چپ همهٔ این مراحل را رد می‌کند. بررسی هنگام بخش‌بندی گرافیم‌ها با یک مقایسه برای هر گرافیم انجام می‌شود و متن کاملاً چپ‌به‌راست از همان مسیر قبلی ترسیم می‌شود، بنابراین فریم‌های کنسول آن بایت‌به‌بایت یکسان هستند.

پشتیبانی متن راست‌به‌چپ در ⁦Text.js 1.0⁩ عرضه می‌شود. بخش بین‌المللی‌سازی ورودی، ویرایش، ترمینال‌ها و API را پوشش می‌دهد.