رفتن به محتوا

bidiscope

  • مخزن: https://github.com/fmoed/bidiscope (فایل package.json به DADiS0/bidiscope اشاره می‌کند) · کامیت خوانده‌شده: 70ff6c5 (۲۰۲۶-۰۴-۲۵) · مجوز: MIT · زبان: TypeScript (بدون وابستگی زمان اجرا) · سکو: کتابخانهٔ JS/Node ≥18 به‌علاوهٔ افزونهٔ xterm.js برای مرورگر
  • دسته: کتابخانه + رندرکنندهٔ لایهٔ رویی (overlay) (@bidiscope/core یک تبدیل رشته است؛ @bidiscope/xterm یک لایهٔ رویی DOM روی xterm.js است)

دو بسته: packages/core (زیرمجموعه‌ای دست‌نویس از UAX #9، یک شکل‌دهندهٔ عربی مبتنی بر فرم‌های نمایشی (presentation forms)، حذف و بازتزریق ANSI، و روش‌های تقریبی «بافت کد» (code-context)) و packages/xterm (افزونه‌ای برای xterm.js که متن جابه‌جاشده به ترتیب دیداری (visual order) را در <div>های DOM روی ردیف‌های راست‌به‌چپ می‌کشد). README چهار بسته را تبلیغ می‌کند. فقط همین دو وجود دارند (ls packages ← core xterm).

چگونه کار می‌کند (تأییدشده در کد)

Section titled “چگونه کار می‌کند (تأییدشده در کد)”
  • خط لوله (core/src/index.ts:57-175): حذف ANSI ← کدنقطه‌ها ← طبقه‌بندی ← بازنویسی اختیاری نوع‌ها بر اساس بافت کد ← سطح پاراگراف ← سطوح جاسازی (embedding levels) ← شکل‌دهی (shaping) به فرم‌های نمایشی ← جابه‌جایی ← بازتزریق ANSI. خروجی یک رشتهٔ visual به‌همراه سطوح و بخش‌ها (runs) است.
  • کلاس‌های دوجهتی (bidi) (core/src/types.ts): دادهٔ UCD ندارد. یک Uint8Array دستی برای U+0000–07FF (:20-173) و حدود ۵۰ بازهٔ دست‌چین برای بالاتر از آن (:194-267) دارد؛ هر چیز دیگری پیش‌فرض L می‌گیرد. نقص‌هایی که در خواندن کد یافتیم:
    • Thaana، NKo و نشانه‌های ترکیبی U+0300–036F در فهرست مسیر کند آمده‌اند، اما مسیر سریع هر cp < 0x800 را زودتر می‌گیرد (:279-281). در نتیجه Thaana (U+0780–07BF) و U+0300–036F از نوع L می‌شوند.
    • فرم‌های نمایشی عبری U+FB1D–FB4F جا افتاده‌اند و به L می‌رسند.
    • کل U+08A0–08FF از نوع AL است، از جمله NSMهای آن (:196).
    • U+FEFF از نوع AL است (:218).
    • Arabic Extended-B، Samaritan، Syriac Supplement، Adlam و چند بلوک دیگر غایب‌اند.
  • الگوریتم (core/src/bidi.ts): قواعد X1 تا X8 از یک پشتهٔ وضعیت جهت‌دار درست با شمارنده‌های سرریز استفاده می‌کنند (:96-256). قواعد W1 تا W7، N0، N1/N2 و I روی کل پاراگراف به‌عنوان یک دنباله اجرا می‌شوند. دنباله‌های بخش ایزوله (isolating run sequences، BD13) وجود ندارد، پس ایزوله‌ها و جاسازی‌ها به حل نوع‌های ضعیف و خنثی نشت می‌کنند:
    • W5 نویسهٔ ET را فقط تا یک همسایه گسترش می‌دهد (:312-329).
    • N1 حالتی را که EN/AN مانند R رفتار می‌کنند نادیده می‌گیرد (:451-466).
    • جفت‌سازی کروشه‌ها در N0 (:355-443) از جدول ۱۰ ردیفی BRACKETS استفاده می‌کند که </> را هم اضافه می‌کند (constants.ts:81-82). این‌ها جزو کروشه‌های جفت UCD نیستند؛ این عدم انطباق آگاهانه و برای کد است.
    • قرینه‌سازی L4 از یک جدول ۱۴ ردیفی استفاده می‌کند (bidi.ts:19-34)، نه از BidiMirroring.txt.
    • L1 و L2 پیاده‌سازی شده‌اند (:489-596).
    • جمع‌بندی: ظاهری شبیه UAX #9 اما ناقص؛ بهترین توصیف، یک روش حدسی (heuristic) دقیق است.
  • آزمون انطباق (core/tests/conformance.test.ts): BidiCharacterTest.txt (که در مخزن گنجانده شده) را اجرا می‌کند، اما فقط قبولی بیش از ۸۵٪ در سطوح و بیش از ۸۰٪ در جابه‌جایی را بررسی می‌کند (:173, :180). BidiTest.txt هم در مخزن هست، ولی هیچ جا به آن ارجاع نمی‌شود.
  • شکل‌دهی (core/src/shaper.ts): جدولی دستی با حدود ۵۰ حرف (عربی به‌علاوهٔ حروف فارسی پ چ ژ ک گ ی و چند حرف اردو) که به فرم‌های نمایشی U+FB50–FDFF / U+FE70–FEFF نگاشت می‌شوند (:78-132)، همراه با لیگاتورهای لام‌الف (:137-143). به‌صورت پیش‌فرض روشن است (index.ts:61-67؛ 'auto' یعنی فرم‌های نمایشی، constants.ts:111-116).
    • باگ: حرفی که فقط از راست متصل می‌شود و از هر دو طرف همسایهٔ اتصال‌پذیر دارد، خانهٔ «میانی» را می‌گیرد. خانهٔ میانی آن 0 است، پس کد به حالت منفرد برمی‌گردد (shaper.ts:166-174, 246-247). بنابراین ا/د/ر/و در میان واژه (مثلاً «باب») به شکل منفرد نمایش داده می‌شوند، نه پایانی.
    • ZWJ (U+200D) غیرمتصل‌کننده تلقی می‌شود. نشانه‌های شفاف فقط تا حدی پوشش داده شده‌اند (:149-160).
  • مدیریت ANSI (core/src/ansi.ts): یک عبارت منظم: \x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Za-z]) (:26).
    • CSIهای دارای <، =، >، : (زیرپارامترهای دونقطه‌ای SGR مانند 38:2::r:g:b) یا بایت‌های میانی (CSI 2 SP q) را از دست می‌دهد. DCS، APC و دیگر دنباله‌های گریز دوبایتی را هم نمی‌گیرد.
    • بایت‌های تطبیق‌نخورده به متن نشت می‌کنند و جابه‌جا می‌شوند.
    • بازتزریق، هر دنباله را در positionMap[origPos] می‌گذارد (:75-108). جفت‌های آغاز/پایان SGR که دور یک بخش راست‌به‌چپ بوده‌اند، در موقعیت‌های دیداری‌ای می‌نشینند که دیگر آن بخش را دربر نمی‌گیرند، پس بازه‌های رنگی به هم می‌ریزند.
    • فضاهای اندیس با هم قاطی شده‌اند. موقعیت‌های حذف، واحدهای UTF-16 را می‌شمارند، positionMap کدنقطه‌های پس از شکل‌دهی را می‌شمارد، و تزریق روی واحدهای UTF-16 پیش می‌رود. نویسه‌های astral یا لیگاتورهای لام‌الف (که اندیس‌ها را جابه‌جا می‌کنند؛ index.ts:149-153, 202-227) دنباله‌های گریز را در جای نادرست می‌نشانند.
  • بافت کد (core/src/code-context.ts): پویشی شبه‌عبارت‌منظم برای نقل‌قول‌ها، توضیحات، مسیرها و URLها. نوع‌های دوجهتی را به‌زور L می‌کند (:145-184). isCodeLine اگر خط با یک حرف ASCII، _، $، # یا // آغاز شود پاراگراف را به‌زور LTR می‌کند (:193-215). یعنی هر خطی که با یک واژهٔ انگلیسی شروع شود LTR می‌شود.
  • افزونهٔ xterm (packages/xterm/src):
    • روی onRender/onWriteParsed/onScroll (با تأخیر debounce شانزده میلی‌ثانیه‌ای)، هر ردیف قابل‌مشاهده را با line.translateToString(true) می‌خواند (processor.ts:57). resolveBidi را با codeContext:true, shaping:true, shapingMode:'presentation-forms', preserveAnsi:false فرا می‌خواند (:65-72). توضیح بالای آن می‌گوید «HarfBuzz handles joining» که با کد در تناقض است.
    • رشتهٔ visual را در یک div مطلقاً جای‌گذاری‌شده به‌ازای هر ردیف، با پس‌زمینهٔ مات و direction:ltr; unicode-bidi:bidi-override می‌کشد (overlay.ts:99-123).
    • بافر xterm هرگز تغییر نمی‌کند (overlay.ts:8-11)، پس کپی متن منطقی را می‌خواند.
    • لایهٔ رویی جریان متن تناسبی است و به خانه‌ها چفت نمی‌شود. cellWidth اندازه‌گیری می‌شود (index.ts:134-139) ولی هرگز در چیدمان به کار نمی‌رود، و لایهٔ رویی فقط یک رنگ پیش‌زمینه دارد (overlay.ts:110-111)، پس همهٔ رنگ‌های SGR از دست می‌روند.
    • برجسته‌سازی انتخاب و مکان‌نما (cursor) همچنان توسط xterm در موقعیت خانه‌های منطقی زیر لایهٔ رویی کشیده می‌شوند.
    • پردازش به‌ازای هر ردیف بافر انجام می‌شود (خط‌های شکسته‌شده پاراگراف‌های جدا می‌گیرند)، از جمله در صفحهٔ جایگزین (alternate screen) (buffer.active).
    • BidiAddonOptions.codeContext/shaping ذخیره می‌شوند (index.ts:51-57) ولی هرگز به پردازشگر داده نمی‌شوند.
لایه وضعیت شاهد
۱ رمزگذاری و یکسان‌سازی ❌ یکسان‌سازی (normalization) ندارد؛ رشته‌های JS وارد می‌شوند؛ فقط toCodepoints (shaper.ts:286-294)
۲ ورودی ❌ هیچ‌کدام از دو بسته مدیریت ورودی ندارند
۳ پهنا ❌ README ادعای «Terminal cell-width calculation ✅»، UAX #11 «Full» و UAX #29 «Grapheme clusters» را دارد (README.md:308, 461-462). هیچ کدی برای پهنا یا خوشهٔ نویسه‌ای (grapheme) وجود ندارد؛ لایهٔ رویی cellWidth را نادیده می‌گیرد
۴ شکل‌دهی ◐ جدول دستی ← فرم‌های نمایشی (shaper.ts)، با باگ میانی←منفرد برای حروف راست‌اتصال؛ حالت 'logical' می‌تواند آن را خاموش کند (index.ts:67)
۵ دوجهتی ◐ زیرمجموعهٔ دست‌ساز UAX #9، بدون دنباله‌های بخش BD13، UCD ناقص (bidi.ts، types.ts)؛ آستانهٔ آزمون ۸۵٪
۶ مکان‌نما، انتخاب و کپی ◐ لایهٔ رویی بافر را منطقی نگه می‌دارد، پس کپی درست است (overlay.ts:8-11)؛ نمایش انتخاب و مکان‌نما با لایهٔ رویی هم‌تراز نیست؛ رشتهٔ visual در core ترتیب دیداری دارد و کپی‌کردنش ناامن است
۷ قلم‌ها ❌ از fontFamily در xterm استفاده می‌کند؛ منطق جایگزینی قلم (fallback) ندارد
۸ مالتی‌پلکسرها ❌ در نظر گرفته نشده
۹ برنامه‌ها و چارچوب‌های TUI ◐ فقط افزونهٔ xterm.js؛ ردیف‌های صفحهٔ جایگزین هم ردیف‌به‌ردیف پوشانده می‌شوند (حاشیه‌های TUI ممکن است به هم بریزند)؛ پوشش‌دهندهٔ CLI/عامل (agent) وجود ندارد
  • «۹۹٫۹۱٪ (91,620/91,707) انطباق با Unicode BiDi» (README.md:22, 400, 460). از روی مخزن قابل بازتولید نیست؛ آزمون در ۸۵٪/۸۰٪ سخت‌گیری می‌کند، و معماری (نبود دنباله‌های بخش ایزوله، جدول کلاس ناقص) رقم ۹۹٫۹٪ را نامحتمل می‌کند. تأییدنشده تلقی شود.
  • UAX #11 «Full»، UAX #29 «Grapheme clusters» و «Terminal cell-width calculation» (README.md:308, 461-462): چنین کدی وجود ندارد.
  • @bidiscope/cli (bidiscope -- claude …، لولهٔ stdin) و @bidiscope/ai-agent (README.md:236-239, 338-348): وجود ندارند؛ نقشهٔ راه آن‌ها را «Coming in Phase 3/4» علامت زده است.
  • گزینه‌های افزونه در README یعنی autoDetect/codeAware (README.md:325-331) با BidiAddonOptions واقعی نمی‌خوانند.
  • «ANSI Escape Preservation ✅» در مقایسه با FriBidi/ICU: عبارت منظم ناقص است؛ بازه‌های رنگی پس از جابه‌جایی جای نادرست می‌گیرند.

آنچه می‌آموزیم یا برمی‌داریم

Section titled “آنچه می‌آموزیم یا برمی‌داریم”
  • ایدهٔ لایهٔ رویی، غریزهٔ درستی است. بافر ترمینال را منطقی باقی می‌گذارد و فقط نقاشی را اصلاح می‌کند، پس کپی و چسباندن درست می‌ماند. برای xterm.js (VS Code، Cursor، ترمینال‌های وب) افزونه‌ای در سطح رندرکننده راه محتملی است. باید خانه‌به‌خانه، با شکل‌دهی HarfBuzz یا مرورگر و یک UAX #9 واقعی (مثلاً ICU4X یا unicode-bidi از طریق WASM) انجام شود، نه با فرم‌های نمایشی.
  • گنجاندن BidiTest.txt و BidiCharacterTest.txt در مخزن و داشتن مهار آزمون انطباقی که نرخ قبولی را گزارش کند الگوی خوبی است. ما با یک موتور واقعی آستانه را ۱۰۰٪ می‌گذاریم.
  • صورت مسئلهٔ «بافت کد» (مسیرها، URLها و رشته‌های نقل‌قول‌شده در نثر آمیختهٔ راست‌به‌چپ که عامل‌های هوش مصنوعی تولید می‌کنند) یک کاربرد واقعی لایهٔ ۹ است. پاسخ درست، ایزوله‌هایی (FSI/PDI) است که تولیدکننده منتشر می‌کند، نه بازنویسی‌های طبقه‌بند.
  • کلید shapingMode: 'logical' می‌پذیرد که رندرکننده‌هایی که شکل‌دهی واقعی دارند نباید فرم‌های نمایشی دریافت کنند.

محدودیت‌ها و الگوهای نادرست

Section titled “محدودیت‌ها و الگوهای نادرست”
  • جایگزینی با فرم‌های نمایشی به‌صورت پیش‌فرض، حتی وقتی مقصد یک DOM مرورگر است که خودش شکل‌دهی بومی دارد.
  • resolveBidi().visual متنی با ترتیب دیداری است که برای چاپ در نظر گرفته شده. هر استفاده از core در CLI یا عامل (آن‌طور که README تبلیغ می‌کند) متن وارونه و ازپیش‌شکل‌داده‌شده را به تاریخچهٔ پیمایش، لاگ‌ها و کلیپ‌بورد می‌برد.
  • جدول یونیکد دستی و دارای شکاف (Thaana، نشانه‌های ترکیبی، فرم‌های نمایشی عبری) به‌جای دادهٔ تولیدشده از UCD.
  • روش‌های تقریبی بافت کد آگاهانه UAX #9 را می‌شکنند (</> به‌عنوان کروشه، خط‌های آغازشده با ASCII به‌زور LTR).
  • بازتزریق ANSI با نگاشت اندیس نمی‌تواند بازه‌های SGR را پس از جابه‌جایی درست نگه دارد.
  • بازاریابی (درصد انطباق، پهنا، CLI، میان‌افزار عامل) بسیار جلوتر از کد است.

یک پروژهٔ مهدی بانشی · mbaneshi.ir