النص من اليمين إلى اليسار في وحدة التحكم
يطبّق Text.js خوارزمية Unicode ثنائية الاتجاه ويجتاز جميع حالات BidiTest.txt البالغ عددها 770,241. تُعرض العبرية والعربية بالترتيب المرئي مع وصل الحروف العربية، بمحاذاة شبكة الخلايا.
يعرض 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(""); }}من الترتيب المنطقي إلى الخلايا
يمر سطر النص من اليمين إلى اليسار بأربع خطوات قبل وصوله إلى وحدة التحكم:
- الالتفاف بالترتيب المنطقي. تنقسم الأسطر في المواضع نفسها التي تنقسم فيها للنص من اليسار إلى اليمين، بحسب عرض الخلايا.
- إعادة ترتيب كل سطر. تحدد المستويات المحسوبة ترتيب عرض الوحدات الكتابية في السطر.
- تشكيل الحروف العربية. تأخذ الحروف أشكالها السياقية (المنفصلة والأولية والوسطية والنهائية) من كتل أشكال العرض العربية. يشغل كل حرف خلية واحدة، وتبقى الأشكال المتصلة حتى مع وجود مؤشر أو تغير لون في وسط الكلمة.
- تثبيت الترتيب. تحتوي الخلايا الآن على الأشكال الرسومية بترتيب العرض.
توجد الخطوة الأخيرة لأن 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 |
العينة الضابطة: استبدال كل شكل رسومي من اليمين إلى اليسار بـ x | 1.67 px |
العينة الضابطة هي الإطار نفسه بنص لاتيني. يحقق برنامج التشغيل المعتمد محاذاة أفضل من العينة الضابطة لأن الرموز التعبيرية داخل مقاطع span من اليمين إلى اليسار تُلاءم معها.
النص من اليسار إلى اليمين بلا تغيير
يتجاوز النص الخالي من محارف اليمين إلى اليسار كل هذه الخطوات. يحدث الفحص أثناء تقسيم الوحدات الكتابية، بمقارنة واحدة لكل وحدة كتابية، ويُعرض النص المكتوب بالكامل من اليسار إلى اليمين عبر المسار نفسه كما في السابق، لذا تتطابق إطاراته في وحدة التحكم بايتًا ببايت.
يتوفر النص من اليمين إلى اليسار في Text.js 1.0. يغطي التدويل الإدخال والتحرير والطرفيات وواجهة API.