هل تصل المبالغ إلى فاتورتك كما كُتبت؟

مقياسٌ مفتوح يبني ملفات PDF فعلية ويقرأ منها الرسوم — لا يفحص ما تُعيده الدوال. 12 حالة، كلٌّ تحمل قاعدةً مسمّاة من خوارزمية يونيكود الثنائية الاتجاه (UAX #9).

السطر نفسه، والخطّ نفسه، ومحرّكان:

مقارنة: pdf-lib تطبع المبلغ 05.432,1 بينما نَسْق تطبعه 1,234.50

هذه صورةٌ مُصيَّرة من ملفَي PDF فعليين، لا نصّ في الصفحة — لأن المتصفّح عارضٌ مطابق لخوارزمية يونيكود، فلو وُضعت السلسلة المكسورة نصّاً لأعاد ترتيبها فأصلحها بصرياً، وضاعت البرهنة. ولّدها evidence.py من الملفات نفسها.

والعيب ليس في تشكيل الحروف — تلك صحيحة في الاثنين — بل في ترتيب المقاطع الاتجاهية.

اللوحة

المحرّكالنتيجة
نَسْق (محرّكنا)
يقطع السطر إلى مقاطع اتجاهية بـUAX #9 ويرسم كلاً في موضعه. bidi-js اجتازت 91,707 حالة من سلسلة يونيكود.
12/12
100%
bidi-shaper → pdf-lib
تُخرج سلسلةً بصرية صحيحة، ثم تُمرَّر إلى drawText. تُقاس هنا في هذا التركيب بعينه.
2/12
16.7%
pdf-lib (كما هي)
المكتبة الأشهر لتوليد PDF في جافاسكربت — 5.5 ألف نجمة. تُشكّل العربية صحيحةً عبر fontkit، ولا تطبّق خوارزمية الاتجاه.
0/12
0%
naqqash + مُهايئها لـpdf-lib
تُصرّح في صفحتها الأولى أنها لا تطبّق الخوارزمية الثنائية الاتجاه. تُقاس هنا على الفاتورة، لا على نطاقها المُعلَن — والفرق مذكور.
0/12
0%

إنصافٌ واجب

naqqash تُصرّح في صفحتها الأولى أنها لا تطبّق الخوارزمية الثنائية الاتجاه، وتُحيل إلى bidi-js. فالنتيجة أعلاه ليست اتهاماً لها بمخالفة ما تَعِد به — بل بيانٌ لما يكلّفه ذلك الحدُّ المُعلَن حين تكون على الورق فاتورةٌ فيها مبلغ. والأمر نفسه في bidi-shaper: تُخرج سلسلةً صحيحة، ثم تعكسها مكتبةُ الرسم بعدها — فالمقياس على التركيب لا على الحزمة وحدها.

الحالات، حالةً حالة

النصّالقاعدةيجب أن يظهر
والخطّ الذي جرت به
نَسْقbidi-shaperpdf-libnaqqash
الإجمالي 1,234.50 ج.م
أشهر عيب يصل العميل: المبلغ مقلوباً على فاتورته.
B1 1,234.50
Almarai
✓ ✗✗✗
المبلغ 9,876.54 B2 9,876.54
Almarai
✓ ✗✗✗
الباقي 15 جنيهاً B1 15
Almarai
✓ ✗✗✗
شركة ABC للتجارة B3 ABC
Almarai
✓ ✗✗✗
الفاتورة رقم INV-2026/001
رقم الفاتورة معرّفٌ قانوني — انعكاسه يجعل المستند غير قابل للمطابقة.
B3 INV-2026/001
Almarai
✓ ✓✗✗
الرقم الضريبي: 310122393500003 B1 310122393500003
Almarai
✓ ✗✗✗
بتاريخ 2026/09/01 B5 2026/09/01
Almarai
✓ ✗✗✗
ضريبة القيمة المضافة ⁨(15%)⁩
بلا عزل تتفرّق «%» و«)» عن الرقم.
B7 (15%)
Almarai
✓ ✗✗✗
الإجمالي ⁨1,571.75 SAR⁩
بلا عزل يسبق مقطعُ العملة الرقمَ في فقرة يمينية.
B7 1,571.75 SAR
Almarai
✓ ✓✗✗
סה״כ 1,234.50 ש״ח
فاتورة عبرية — العيب نفسه بلا تغيير.
B8 1,234.50
Arial
✓ ✗✗✗
مبلغ کل 1,234.50 ریال B8 1,234.50
Almarai
✓ ✗✗✗
کل رقم 1,234.50 روپے B8 1,234.50
Almarai
✓ ✗✗✗

القواعد — الحَكَم يونيكود لا نحن

B1 الرقم داخل نصّ عربي
الأرقام في فقرة يمينية تُعرض بترتيبها من اليسار إلى اليمين (قاعدتا I1/I2 بعد W2). فـ«1,234.50» تُقرأ كما كُتبت، ولا تنعكس مع الفقرة.
B2 الفاصلة والنقطة داخل الرقم
الفاصل المشترك (CS) بين رقمين من الصنف نفسه يصير منهما (قاعدة W4). فـ«1,234.50» رقمٌ واحد لا ثلاثة، ولا تتفرّق فواصله.
B3 اللاتينية داخل نصّ عربي
المقطع اللاتيني مستوى زوجي داخل فقرة فردية، فيُعرض يساراً-يميناً كما كُتب. «ABC» لا «CBA».
B4 قلب الأقواس
قاعدة L4: المحرف المتناظر يُرسم بنظيره داخل مقطع يميني. فالقوس الذي كُتب «(» يظهر «)» ما لم يُعزل.
B5 الشرطة المائلة تلتحق بالرقم
الشرطة المائلة فاصلٌ مشترك (CS)، فتلتحق بالرقمين حولها (W4). فـ«2026/09/01» يبقى بترتيبه.
B6 الشرطة لا تلتحق بالرقم العربي الصنف
الأرقام بعد حرف عربي تصير AN (قاعدة W2)، والشرطة ES لا تلتحق بـAN (W4) فتبقى محايدة. فـ«⁦2026-09-01⁩» يُعرض «⁦01-09-2026⁩» — سلوكٌ صحيح يُنتجه كل عارضٍ مطابق.
B7 العزل يحمي المقطع
محارف العزل (U+2066..U+2069) تمنع المحايدات على الحدّ من التسرّب إلى الجوار (X5a-X6a). فـ«(15%)» معزولةً تبقى كما كُتبت.
B8 العيب ليس عربياً
الخوارزمية الثنائية الاتجاه لا تعرف لغةً بعينها، بل أصنافَ محارف. فكل كتابةٍ من اليمين إلى اليسار — العبرية والفارسية والأردية والسريانية — يصيبها ما يصيب العربية بالضبط.

سلوكٌ موثَّق لا يُسجَّل عليه

ما لا يقيسه هذا المشغّل قياساً تامّاً لا يُمنح عليه درجة.

بتاريخ ⁦2026-09-01⁩

يُعرض: ⁦01-09-2026⁩

ليست إخفاقاً: هذا ترتيب يونيكود الصحيح، ويُنتجه كل عارضٍ مطابق ومنه المتصفّح. مذكورةٌ هنا لأنها تفاجئ من يراها على فاتورته، والعلاج استعمال «/» أو العزل.

المبلغ (نقداً)

يُعرض: القوس المكتوب «(» يُرسم برسم «)» والعكس

أُخرجت من التسجيل لأن هذا المشغّل لا يستطيع قياسها: القلب يُبدّل رسمَي القوسين، فتبقى مجموعة الرسوم المرسومة كما هي ولا يكشفه وجودُ رسم. وكانت الحالة تمرّ لمجرّد ظهور قوسٍ ما — فحصٌ يمنح درجةً بلا معنى. ولا يُسجَّل ما لا يُقاس؛ والقلب مُختبَرٌ في نَسْق على مستوى المقاطع.

أعِد تشغيله بنفسك

git clone https://github.com/opusstudiohq-max/arabic-invoice-mcp
cd arabic-invoice-mcp/pdf-benchmark
npm install
node run.mjs

المشغّل يبني الملفات ويقرأ منها، ويكتب results.json. وهذه الصفحة مولَّدة منه — لا رقم فيها مكتوبٌ بيد. والخطّ يُختار لكل حالة: أول خطٍّ يغطّي محارفها كلها، ويُسجَّل مع نتيجتها. فتشغيلُ حالةٍ عبرية بخطٍّ لا يحمل العبرية يرسم مربّعاتٍ ويقيس الأرقام وحدها، ثم تُعرض النتيجة نجاحاً — وذلك تضليل. الخطوط في هذا التشغيل: Almarai، Arial.

وجدتَ خطأً في حالة؟ أو محرّكاً يستحق القياس؟

كل حالة هنا تحمل قاعدتها، فمن يخالفها فليخالف القاعدة علناً. افتح مسألةً — نُصحّح علناً ونذكر من صحّح.

نعدّ الزيارات بلا كوكيز ولا هوية — عدداً ومساراً لا أكثر. والفاتورةُ التي تكتبها لا تغادر متصفّحك، ولم يتغيّر ذلك.