← בלוג

טקסט מימין לשמאל במסוף

⁦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 מבודד את ה-span כפי שעושה <bdi>, כך ששם משתמש שכיוונו אינו ידוע לא יכול לשנות את סדר הטקסט שסביבו.

האלגוריתם

התמיכה בכתיבה מימין לשמאל היא מימוש של אלגוריתם הדו-כיווניות של 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⁩, שגם הוא בעל רוחב קבוע, עם 7.2012 px לגליף בגודל 12 px לעומת 7.2246 px ב-Menlo. הוא צר יותר ב-⁦0.3%⁩, מספיק כדי להסיט שורה שלמה בעברית בכחצי תא.

CssConsoleDriver מודד כל span מימין לשמאל באמצעות canvas בגופן המסוף, שמשתמש באותו מנגנון בחירת גופן חלופי של Blink כמו DevTools, ומוסיף ל-span ריווח לפי ההפרש המדויק ב-LayoutUnits (1/64 px). הרוחב הסופי של כל span זהה לרוחב תאי הרשת שהוא מכסה. span מכווץ לכל היותר בחצי תא, כך שגופן חלופי רחב בהרבה חורג מהשטח שלו במקום לחפוף ל-span הבא.

bun bench/devtools/bidi-shot.ts מודד את התוצאה בממשק DevTools האמיתי, בפריים בגודל ⁦64×21⁩ עם שורות בעברית, בערבית, שורות מעורבות ושורות עם אמוג'י בתוך עברית:

פרייםסטיית הקצה הימני בין השורות
תאים שנכתבו למסוף ללא קיבוע14.5 px
קיבוע ללא התאמת רוחב2.22 px
הדרייבר בגרסה המופצת0.156 px
ביקורת: כל גליף מימין לשמאל הוחלף ב-x1.67 px

הביקורת היא אותו פריים עם טקסט לטיני. הדרייבר בגרסה המופצת מיישר טוב יותר מהביקורת כי אמוג'י בתוך spans מימין לשמאל עוברים התאמת רוחב יחד איתם.

טקסט משמאל לימין נשאר ללא שינוי

טקסט ללא תווים מימין לשמאל מדלג על כל זה. הבדיקה מתבצעת בזמן הפילוח לגרפמות, השוואה אחת לכל גרפמה, וטקסט שכולו משמאל לימין מצויר באותו מסלול כמו קודם, כך שהפריימים שלו במסוף זהים ברמת הבתים.

התמיכה בטקסט מימין לשמאל נכללת ב-⁦Text.js 1.0⁩. בינאום מכסה קלט, עריכה, טרמינלים ואת ה-API.