السلام عليكم
في الموضوع السابق تحدثنا عن Quran.ws والمكوّنات التي نحاول توفيرها للمطورين حتى لا يبدأ كل مشروع قرآني من الصفر.
أحد هذه المكوّنات هو quran-svg-elements: صفحات المصحف نفسها بصيغة SVG، لكن بعد تفكيكها برمجيًا إلى كلمات وعلامات وعناصر يمكن الوصول إليها والتعامل معها.
هذا حلّ لنا مشكلة مهمة جدًا: أصبحنا نعرف كل عنصر من الصفحة ماهو . هذه المسارات كلمة، وهذه فتحة، وهذه همزة وصل، وهذه الكلمة هي 2:255:3.
لكن بقي سؤال آخر:
كيف نأخذ هذه الدقة نفسها، ونجعلها عملية وسريعة داخل تطبيق حقيقي على الويب وiOS وأندرويد وFlutter وغيرها؟
من هنا بدأ quran-engine.
المشكلة: صفحة المصحف ليست صورة وليست نصًا
عند بناء تطبيق مصحف، نريد شيئين في الوقت نفسه:
أن تظهر الصفحة كما طُبعت: الرسم نفسه، والأسطر نفسها، والضبط وعلامات الآيات في مواضعها.
وفي الوقت نفسه نريدها تفاعلية: يمكن لمس كلمة، وتلوين آية، وتتبع التلاوة، وإخفاء مواضع للحفظ، والبحث، وقص آية أو تصديرها.
الحلول المعتادة تعطي جزءًا من هذا فقط.
| الطريقة | ما تقدمه | أين تظهر المشكلة |
| صورة PNG/WebP | مطابقة بصرية ممتازة | لا تعرف شيئًا عن الكلمات والآيات، والتكبير محدود |
| الخطوط | نص حي وخفيف | التخطيط تحدده محركات النص، والتحكم الدقيق في عناصر الكلمة محدود |
| SVG | رسم متجهي مطابق وقابل للتفاعل | ثقيل، وDOM كبير، والمنطق يجب أن يُبنى فوقه |
ثم تظهر مشكلة ثانية عند الانتقال من تجربة على الويب إلى منتج حقيقي:
هل نعيد كتابة كل هذا المنطق لكل منصة؟
تحديد الكلمة التي لمسها المستخدم، وتظليل آية تمتد عبر عدة أسطر، وإخفاء أجزاء من الصفحة، والبحث، والتلوين على مستوى حركة واحدة؛ هذه ليست مجرد عمليات رسم.
إذا كُتبت مرة في JavaScript، ثم أعيدت في Swift وKotlin وDart، يصبح لدينا أكثر من تنفيذ لنفس السلوك، ومع الوقت تبدأ هذه التطبيقات بالاختلاف عن بعضها.
لهذا قررنا أن يكون هناك محرك واحد.
الفكرة: المحرك يقرر، والمنصة ترسم
المبدأ الذي بني عليه quran-engine بسيط:
المحرّك يقرّر، والمنصة ترسم.
هناك نواة واحدة مكتوبة بلغة Rust تتولى منطق الصفحة:
- تحميل البيانات والهندسة
- تحديد الكلمات والآيات
- hit testing
- التحديد والتظليل
- الأنماط والألوان
- الإخفاء والكشف
- البحث
- القص والتصدير
- بيانات السور والأجزاء والأحزاب
- الربط مع التلاوة
أما SDK كل منصة، فتبقى رقيقة قدر الإمكان: تأخذ الناتج من المحرك وترسمه باستخدام أدوات المنصة نفسها.
على الويب: Canvas2D.
على iOS: Core Graphics.
على Android: أدوات الرسم الأصلية.
وفي Flutter وReact Native تمر البيانات عبر أغلفة خفيفة للمحرك نفسه.
وهكذا يبقى القرار في مكان واحد بدل أن نعيد اختراعه في كل منصة.
Quran SVG Elements
│
▼
qvp-convert
│
▼
.qvp page files
│
▼
qvp-core (Rust)
│
├── Web
├── iOS
├── Android
├── Flutter
└── React Native
QVP: صيغة ابتكرناها تجعل كل صفحة تعرف ماذا ترسم
SVG ممتاز في وصف الشكل، لكنه لا يعرف بالضرورة معنى هذا الشكل.
قد يعرف أن الصفحة تحتوي على مجموعة من المسارات، لكنه لا يعرف أن ثلاثة منها تكوّن كلمة، أو أن مسارًا صغيرًا هو فتحة، أو أن هذه الكلمة هي الكلمة الثالثة من آية معينة.
لهذا احتجنا إلى صيغة تحمل الهندسة والبنية معًا.
سميناها QVP — Quran Vector Page.
ملف الصفحة يعرف مثلًا:
- حدود الكلمات والأسطر
- رقم السورة والآية والكلمة
- أجزاء الآية عند امتدادها بين الأسطر
- المسارات التابعة لجسم الكلمة
- علامات الضبط وأسماؤها
- علامات الآيات والعناصر الزخرفية
- النص المرتبط بكل كلمة
أي أن الصفحة لم تعد مجرد رسم متجهي، بل أصبحت رسمًا مفهومًا برمجيًا.
لم نعد رسم المصحف
هذه نقطة أساسية في المشروع.
quran-engine لا يعيد تشكيل الكلمات بخط، ولا يحاول تقليد صفحة المصحف.
الرسم يأتي أصلًا من quran-svg-elements.
ودور المحول هو نقل تلك الهندسة إلى QVP دون إعادة رسمها أو تبسيطها.
للتحقق من ذلك، يمكن تحويل الصفحة:
SVG → QVP → SVG
ثم رسم الأصل والنسخة الناتجة ومقارنتهما بكسلًا ببكسل.
الصفحات الـ604 تجتاز بوابة المطابقة المحددة للمشروع، مع حد فرق أقل من 0.05% من البكسلات.
وهذا مهم بالنسبة لنا لأن الأداء لا ينبغي أن يأتي على حساب شكل المصحف.
العلاقة مع quran-svg-elements
يمكن النظر إلى المشروعين باعتبارهما مرحلتين لمشكلة واحدة.
quran-svg-elements
يجيب عن السؤال:
ما هذا العنصر؟
هذه كلمة.
هذه فتحة.
هذه علامة آية.
وهذه الكلمة هي 2:255:3.
quran-engine
يجيب عن السؤال التالي:
ماذا يمكن أن أفعل بهذه العناصر داخل تطبيق حقيقي؟
كيف أعرف الكلمة التي لمسها المستخدم؟
كيف ألون حركة بعينها؟
كيف أتتبع التلاوة؟
كيف أخفي كلمات للحفظ؟
كيف أعرض الصفحة بكفاءة على الهاتف؟
لهذا فالمشروعان يتكاملان:
Elements يعرّف الحبر، وEngine يجعل التعامل معه سريعًا وموحدًا عبر المنصات.
وإذا اكتشف المحرك مشكلة في البيانات الأصلية، فلا نصححها داخل المحرك. تعود المشكلة إلى المصدر في quran-svg-elements، ثم يعاد توليد البيانات.
نريد أن تبقى هناك نسخة واحدة من الحقيقة.
لماذا لم نستخدم SVG مباشرة؟
من الممكن استخدام quran-svg-elements مباشرة في الويب، وفي بعض الحالات هذا هو الحل الأنسب.
لكن عند الانتقال إلى تطبيقات الهاتف والأجهزة الأضعف، تظهر تكلفة الحجم والتحليل والتعامل مع عدد كبير جدًا من عقد SVG.
في إحدى الصفحات المستخدمة للقياس:
| الشكل | الحجم |
| SVG خام | 768 KB |
| SVG + gzip | 190 KB |
| SVG + brotli | 119 KB |
| QVP خام | 142 KB |
| QVP + gzip | 71 KB |
| QVP + brotli | 63 KB |
أي أن الصفحة نفسها تصبح قرابة 5.4× أصغر من SVG الخام، وحوالي 1.9× أصغر من SVG بعد Brotli.
أما المصحف كاملًا:
- صفحات QVP الخام: نحو 81.5 MB
- بعد Brotli: نحو 37 MB
- متوسط الصفحة الخام: نحو 135 KB
- أطلس السور والأجزاء والأحزاب والأرباع كاملًا: قرابة 10 KB
والأهم أن التطبيق لا يحتاج إلى تنزيل المصحف كاملًا؛ كل صفحة ملف مستقل يمكن تحميله عند الحاجة.
ماذا عن السرعة؟
لم يكن هدف QVP تقليل الحجم فقط.
لأن الهندسة تصل جاهزة إلى المحرك، لا نحتاج في كل مرة إلى تشكيل النص، واختيار glyphs، وتخطيط الكلمات والأسطر للوصول إلى شكل الصفحة.
في القياسات الحالية على بناء Release:
| العملية | الزمن |
| تحميل الصفحة وبناء الهندسة | 1 ms |
| تحديد اللمس على الشكل الفعلي | 0.7 µs |
| تحديد اللمس المراعي للفراغات | 0.07 µs |
| البحث عن لفظ «الله» في الصفحة | 17 µs |
| بناء تظليل آية تمتد ستة أسطر | 48 µs |
وقسنا أيضًا المسار الكامل حتى ظهور الصفحة لأول مرة، بالمقارنة مع عرضها بالطريقة التقليدية باستخدام الخط والتخطيط:
| الصفحة | خط + تخطيط + رسم | QVP |
| 42 | 32.3 ms | 5.6 ms |
| 300 | 30.6 ms | 5.7 ms |
| 585 | 36.6 ms | 6.5 ms |
في هذه الاختبارات كان QVP أسرع بنحو 5–6 مرات في أول تجهيز ورسم للصفحة.
ما الذي أصبح ممكنًا؟
الميزة الحقيقية ليست أن الصفحة ترسم بسرعة فقط، وإنما أن المحرك يفهم ما يرسمه.
لمس الكلمة
يمكن للمستخدم لمس الصفحة، ويعيد المحرك الكلمة المقصودة.
ولأن المسافات في المصحف ليست شبكة هندسية منتظمة، لا يعتمد المحرك على مستطيلات ساذجة حول الكلمات، بل يراعي شكل الحبر والأسطر والفراغات بينها.
التلوين حتى مستوى الحركة
لأن العلامات مفصولة ومعرّفة، يمكن استهداف:
word → mark → fathah
وليس فقط تلوين كلمة كاملة.
هذا يفتح الباب لأشياء مثل تلوين التجويد أو التعليم التفاعلي على مستوى أكثر دقة.
تظليل الآيات
إذا امتدت آية عبر عدة أسطر، لا يتعامل معها المحرك كمجموعة مستطيلات منفصلة، بل يمكنه إنشاء شريط تظليل متصل يتبع امتداد الآية.
الحفظ والمراجعة
يمكن إخفاء كلمات أو آيات، أو تمويهها، ثم كشفها تدريجيًا.
وهذه القدرات تأتي من المحرك نفسه، فلا يحتاج كل تطبيق إلى إعادة تنفيذها.
البحث
المحرك يستطيع البحث باستخدام النصوص المرافقة للكلمات، مع تطبيع يناسب الاختلاف بين صور الكتابة.
التلاوة
يمكن ربط الكلمات بتوقيتات التلاوة ثم تتبعها أثناء التشغيل، مع التحقق من تطابق عدد الكلمات حتى لا ينحرف التتبع بصمت عند وجود اختلاف في البيانات.
القص والتصدير
يمكن اختيار آية أو جزء من الصفحة وإخراجه كـSVG مستقل، مع الألوان والتظليل والأنماط الحالية.
محرك واحد
بين Rust والمنصات يوجد C ABI موحد.
الفكرة ليست فقط تسهيل الربط مع اللغات المختلفة، بل أن تبقى واجهة المحرك واحدة.
إذا أضفنا قدرة جديدة، يفترض أن تمر في السلسلة نفسها:
qvp-core
↓
C ABI
↓
Web
Android
Flutter
iOS
React Native
وبالأسماء والمفاهيم نفسها قدر الإمكان.
بهذا يصبح لدينا محرك واحد ووثائق واحدة، لا خمسة محركات تحمل الاسم نفسه لكنها تتصرف بشكل مختلف.
لماذا Rust؟
ليس الهدف من Rust بحد ذاته هو المشروع.
كان المطلوب نواة:
- سريعة
- صغيرة
- لا تعتمد على منصة معينة
- يمكن استدعاؤها من C ABI
- تعمل أيضًا عبر WebAssembly
- ويمكن اختبارها مرة واحدة ثم استخدامها في كل مكان
Rust كان مناسبًا لهذا الدور، بينما تبقى واجهة المستخدم والرسم بلغات وأدوات كل منصة.
المشروع ما زال في بدايته
المحرك يعمل، والأغلفة الأساسية موجودة، لكن هذه المرحلة بالنسبة لنا ليست نهاية المشروع.
بل العكس.
الشيء الذي نحتاجه الآن أكثر من أي شيء آخر هو أن يُستخدم في تطبيقات حقيقية.
يمكننا اختبار عشرات الحالات التي نعرفها، لكن الاستخدام الحقيقي سيكشف حالات لم نفكر فيها، ومشكلات في الـAPI، وقدرات ناقصة، وفروقًا في الأداء بين الأجهزة والمنصات.
جرّبه وساعدنا في تطويره
إذا كنت تعمل على تطبيق قرآن أو تريد تجربة المحرك:
المستودع:
https://github.com/quran-ws/quran-engine
يمكنك المساهمة بأكثر من طريقة:
جرّبه.
شغّل أحد تطبيقات العرض، أو استخدمه في تجربة صغيرة على منصتك.
أرسل القياسات.
خصوصًا من أجهزة Android المتوسطة، وأجهزة iPhone الأقدم، والحواسيب الضعيفة. قياس من جهاز حقيقي مساهمة مهمة بالنسبة لنا.
اقترح ما تحتاجه.
إذا كان تطبيقك يحتاج وظيفة لا يوفرها المحرك، افتح Issue واشرح حالة الاستخدام.
راجع الـAPI والتوثيق.
إذا كان شيء غير واضح أو اسم غير مناسب، نريد معرفته.
ارفع Pull Request.
إصلاح، اختبار، تحسين للتوثيق، تحسين أداء، أو حتى غلاف لمنصة جديدة.
ونرحب خصوصًا بمن يريد تجربة C ABI وبناء wrapper للغة أو منصة لم ندعمها بعد.
وجزاكم الله خيرًا