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 است)
این پروژه چیست
Section titled “این پروژه چیست”دو بسته: 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 و چند بلوک دیگر غایباند.
- Thaana، NKo و نشانههای ترکیبی U+0300–036F در فهرست مسیر کند آمدهاند، اما مسیر سریع هر
- الگوریتم (
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) دقیق است.
- W5 نویسهٔ ET را فقط تا یک همسایه گسترش میدهد (
- آزمون انطباق (
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) دنبالههای گریز را در جای نادرست مینشانند.
- CSIهای دارای
- بافت کد (
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) ولی هرگز به پردازشگر داده نمیشوند.
- روی
پوشش لایهها
Section titled “پوشش لایهها”| لایه | وضعیت | شاهد |
|---|---|---|
| ۱ رمزگذاری و یکسانسازی | ❌ | یکسانسازی (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) وجود ندارد |
ادعاشده اما تأییدنشده
Section titled “ادعاشده اما تأییدنشده”- «۹۹٫۹۱٪ (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، میانافزار عامل) بسیار جلوتر از کد است.