ملاحظة: هذه تجربة تقنية بحتة، وليست إعلاناً عن انتقال نهائي أو منتج جاهز. فرع develop-svg، الإصدار 7.0.0 من open-mushaf-native.
https://youtube.com/shorts/zl268HoQGvo---
1. البداية: لماذا جربنا الانتقال أصلاً
لم يكن الدافع أن PNG صار عبئاً لا يُحتمل. الحقيقة أن صور PNG لدينا كانت (ولا تزال) محسّنة بعناية، الرواية الواحدة لا تتجاوز 70 ميغابايت تقريباً، وهو رقم جيد جداً لمجموعة صور بهذه الدقة. الدافع الحقيقي كان الفضول: أردنا أن نختبر هل يمكن لتقنية SVG أن تعمل بشكل جيد عبر منصات متعددة (الويب، أندرويد، iOS) باستخدام React Native، وهل ستمنحنا مرونة لا توفرها الصور النقطية.
كانت هناك مشكلتان حقيقيتان دفعتانا لتجربة البديل:
الأولى جمالية: الخلفية البيضاء الثابتة لصور PNG رفضت التعايش مع أي مظهر ليلي أنيق، مهما حاولنا تطويعها بحيل CSS أو طبقات تراكب.
الثانية بنيوية: كنا نعتمد على ملفات JSON خارجية تحمل إحداثيات بكسلية يدوية لتحديد مواضع الآيات، وهي آلية هشة تنكسر مع أي تغيير في الأبعاد أو حجم الشاشة (سنعود لتفاصيل هذه المشكلة بعد قليل).
أردنا أن نرى: هل SVG يحل هاتين المشكلتين فعلاً؟ بحثنا قادنا إلى مكتبة quran-svg من Quranpedia.
2. اكتشاف quran-svg
المكتبة قدّمت صفحات مصحف كاملة كرسوميات متجهة، جاهزة لعدة روايات: حفص، ورش، قالون، الدوري، وغيرها. كل ملف SVG يحمل بداخله الخط العثماني مرسوماً كمسارات متجهية خالصة، وعناصر <path class="ayahPolygon"> ترسم حدود كل آية بدقة هندسية، وبيانات وصفية مدمجة مثل أرقام الآيات.
الفكرة التي أشعلت حماسنا: طالما أن المضلعات جاهزة داخل الملف نفسه، فلن نحتاج بعد الآن لخرائط إحداثيات خارجية لربط التفسير بالآية.
توقفنا عند أول عقبة: المستودع لا يحمل ملف ترخيص (LICENSE) واضحاً. غموض قانوني تعاملنا معه بحذر، مضينا في التجربة لكن أبقينا تنبيهاً صريحاً في توثيق المشروع إلى أن يتضح الموقف.
3. هندسة جديدة من الصفر: إعادة بناء لا تعديل
بمجرد أن قررنا تجربة الاستغناء عن الصور المحلية، وجدنا أنفسنا لا نُعدّل على البنية القديمة، بل نعيد بناء العمارة من الصفر. لماذا نُحمّل المصحف كله دفعة واحدة إذا كان بإمكاننا جلب ما يحتاجه المستخدم فقط، في اللحظة التي يحتاجه فيها؟
كيف تجنبنا استضافة البيانات على خوادمنا
القرار الأهم في إعادة البناء كان تقنياً بقدر ما هو مالياً: بدلاً من استضافة مئات الميغابايتات من ملفات SVG على خوادمنا الخاصة (وتحمّل تكاليف نقل البيانات المتصاعدة مع كل مستخدم جديد)، اعتمدنا على jsDelivr كشبكة توصيل محتوى مجانية للمشاريع مفتوحة المصدر.
الفكرة بسيطة لكنها ذكية: jsDelivr يمرّي أي مستودع GitHub تلقائياً عبر أكثر من 750 نقطة توزيع حول العالم، مع دعم ضغط brotli وgzip، ودون حدود استخدام تُذكر عملياً. هذا يعني أننا نحصل على أداء شبكة CDN احترافي، دون أن ندفع فلساً واحداً أو نُدير خادماً واحداً.
نقطة تقنية مهمة هنا: لم نعتمد رابط raw.githubusercontent.com مباشرة، لأن GitHub نفسها توضّح أن هذه الخدمة ليست CDN، وهي محدودة بمعدل طلبات منخفض جداً (نحو 60 طلباً في الساعة لكل عنوان IP)، وهو رقم غير كافٍ إطلاقاً لتطبيق ينشط عليه آلاف المستخدمين في وقت واحد.
كما أننا لم نُشِر إلى الفرع main مباشرة، بل ثبّتنا رابط CDN على commit SHA محدد. السبب: ملفات الإحداثيات (JSON) مرتبطة تماماً بتخطيط ملفات SVG في تلك اللحظة بالذات. لو أعاد المستودع الأصلي هيكلة بنية المصحف يوماً ما، فستنزاح إحداثيات المضلعات، وسيسقط كل تظليل آية في المكان الخطأ. لذلك جعلنا تحديث الـ SHA قراراً واعياً يُراجَع يدوياً، لا تحديثاً تلقائياً صامتاً.
هكذا يبدو الأمر عملياً في الكود (نسخة مختصرة):
const PINNED_SHA = '1525aa7d6e94a4c17a051302767331ef500308ec';
const DEFAULT_CDN_BASE = `https://cdn.jsdelivr.net/gh/quranpedia/quran-svg@${PINNED_SHA}`;
export const QURAN_SVG_CDN_BASE =
process.env?.EXPO_PUBLIC_QURAN_SVG_CDN || DEFAULT_CDN_BASE;
export function quranSvgPageUrl(qiraa, page) {
const path = RIWAYA_TO_UPSTREAM_PATH[qiraa];
const padded = String(page).padStart(3, '0');
return `${QURAN_SVG_CDN_BASE}/mushafs/${path}/svg/${padded}.svg`;
}
الرابط النهائي لصفحة واحدة يصبح ببساطة عنوان jsDelivr مبنياً على الـ SHA المثبت واسم الرواية ورقم الصفحة، مع إمكانية تجاوز هذا الرابط الافتراضي عبر متغيّر بيئة أثناء التطوير المحلي، للاختبار على مرآة محلية مثلاً دون المساس بالكود.
التحدي الأصعب: إدارة تخزين مختلفة تماماً على الويب مقابل الهاتف
هنا واجهنا واحدة من أكثر المشكلات إرهاقاً في التجربة كلها. تطبيقنا يعمل على الويب وأندرويد وiOS في آن واحد، وكل بيئة من هذه البيئات تتعامل مع التخزين المحلي بمنطق مختلف كلياً.
على الويب، لا يوجد نظام ملفات حقيقي نتحكم فيه، بل حصة تخزين (storage quota) يديرها المتصفح نفسه، وقد يرفض المتصفح منح مساحة إضافية أو حتى يحذف البيانات المخزنة إن اقترب التطبيق من الحد الأقصى، دون تحذير مسبق واضح دائماً. اضطررنا للاعتماد على واجهة navigator.storage لمراقبة الحصة المتاحة وتنبيه المستخدم قبل أن يصطدم بالجدار.
على الهاتف (أندرويد وiOS)، الوضع مختلف جذرياً: نظام ملفات حقيقي عبر expo-file-system، لكن مع تحديات أخرى، مثل استئناف التحميل المنقطع (إن أغلق المستخدم التطبيق منتصف تنزيل رواية كاملة)، والتحقق من اكتمال الملفات وسلامتها بعد التنزيل، وتوزيع عمليات القراءة والكتابة دون تجميد واجهة المستخدم.
النتيجة كانت طبقة تجريد (abstraction layer) كاملة كتبناها من الصفر، توحّد التعامل مع التخزين خلف واجهة واحدة، بينما تختفي خلفها كل هذه الاختلافات. فوق هذه الطبقة، بنينا استراتيجية "حمّل ما تحتاج فقط": الصفحة المطلوبة تُجلب من الشبكة عند الحاجة لا قبلها، وإن كانت الرواية محفوظة مسبقاً للاستخدام دون اتصال، تُقرأ مباشرة من التخزين المؤقت، مع شاشة مستقلة لإدارة المحتوى المُنزَّل يرى فيها المستخدم الروايات المحفوظة وأحجامها ويحذف منها ما يشاء.
لتحسين تجربة الاستخدام هنا أكثر، أضفنا حساب حجم تقديري لكل رواية، مبني على الحجم الفعلي لملفات SVG التي سيتم تنزيلها، إلى جانب مؤشر إجمالي عام لحجم كل البيانات المُنزّلة في التطبيق مجتمعة. كما فصلنا التفاسير عن الروايات في قائمتين منسدلتين (collapsible) مستقلتين، بدلاً من قائمة طويلة واحدة مربكة، حتى يستطيع المستخدم أن يرى بوضوح ما يشغل مساحته، هل هي روايات القراءة أم ملفات التفسير، ويقرر بسهولة أكبر ما يحتفظ به وما يحذفه.
4. الوداع للإحداثيات اليدوية، وثمن الطريقة القديمة
في العمارة القديمة (قبل SVG)، لم تكن المشكلة فقط أن ملفات JSON للإحداثيات هشة، بل أن التطبيق كان يُعيد حساب هذه الإحداثيات ديناميكياً في كل مرة يتغيّر فيها حجم الشاشة أو تُفتح صفحة جديدة. كنا نحتفظ بإحداثيات مرجعية مبنية على دقة عرض معينة (reference resolution)، ثم عند كل عرض فعلي نُطبّق معادلة تحجيم وإزاحة (scale & offset) لتحويل تلك الإحداثيات إلى القياس الفعلي لشاشة المستخدم.
هذه العملية كانت مرهقة على أكثر من صعيد: حسابياً، لأنها تتكرر مع كل تغيير في حجم النافذة أو دوران الجهاز أو تبديل الرواية. ومن ناحية الدقة، لأن أي خطأ بسيط في معامل التحجيم يعني أن الضغط على كلمة يُخرج آية مجاورة بدلاً من الآية الصحيحة. ضبط هذه المعاملات يدوياً والتأكد من صحتها عبر أحجام شاشات مختلفة استهلك وقتاً ومجهوداً كبيرين، دون أن نصل أبداً إلى دقة مضمونة بنسبة 100 بالمئة.
مع SVG، استبدلنا هذا كله بتقنية point-in-polygon تعمل مباشرة على مضلعات ayahPolygon المرفقة داخل كل صفحة، دون أي حاجة لإعادة حساب أو تحجيم يدوي. اضغط مطولاً على أي كلمة، ويتعرّف التطبيق فوراً على الآية الصحيحة، بصرف النظر عن حجم الشاشة أو الرواية المختارة. اختفاء ملفات JSON المخصصة للإحداثيات لم يكن مجرد تبسيط تقني، بل تحوّلاً حقيقياً: صيانة أقل بكثير، دقة اختيار شبه كاملة، ودعم تلقائي لأي رواية جديدة تُضاف مستقبلاً دون أي إعداد إضافي من جانبنا.
5. حين تتألق الروايات والثيمات معاً
أضفنا واجهة إعدادات تتيح تبديل الرواية (حفص، ورش، قالون، وغيرها) بشكل حيوي وسلس، معتمدين على مكتبة react-native-svg لعرض الصفحات مع استجابة كاملة للمس.
وبما أن خلفية SVG شفافة بطبيعتها، وجدنا أن الثيمات باتت تعمل بشكل أجمل من أي وقت مضى. الوضع الليلي أصبح يطبّق لون الخلفية مباشرة دون أي تشويه بصري، بينما كانت صور PNG قديماً تترك إطاراً أبيض عنيداً يصعب إخفاؤه مهما حاولنا. الأهم من ذلك: أصبح بإمكاننا تغيير "ملمس" الخلفية نفسها بحسب الثيم، خلفية بيضاء ناصعة، أو لون ورقي دافئ يحاكي صفحة مصحف حقيقي، أو لون داكن للوضع الليلي، وكلها تعمل بشكل سليم خلف الرسم المتجهي دون أي بقايا بصرية من الطبقة القديمة.
6. الكابوس الأسود: حين تحوّلت الصفحات إلى عتمة
بعد أسابيع من التقدّم المُرضي، ظهرت مشكلة أربكتنا حقاً: صفحات الروايات غير حفص بدأت تظهر بخلفية سوداء معتمة تطمس النص بالكامل.
استغرق التحقيق أسبوعاً كاملاً، حتى اكتشفنا أن السبب يكمن في عناصر <path class="ayahPolygon"> نفسها. في رواية حفص، كانت خاصية fill-opacity="0" موجودة مسبقاً داخل كل مضلع، فتبقى شفافة تلقائياً. أما بقية الروايات، فقد افتقرت إلى هذه الخاصية، فسقطت في التعبئة السوداء الافتراضية التي تغطي الصفحة كاملة.
طبّقنا حلاً مؤقتاً على مستوى تطبيقنا (دالة صغيرة تُضيف الخاصية الناقصة قبل العرض)، لكننا لم نكتفِ بذلك. فتحنا issue في مستودع quran-svg نفسه لإبلاغ الصيانين بالمشكلة عند المصدر، لأن الحل الحقيقي يجب أن يكون في الملفات الأصلية نفسها، لا في تصحيح كل تطبيق يستهلكها على حدة.
7. تحدٍّ إضافي لم نتوقعه: صعوبة قراءة الرسم العثماني القديم في ورش
من الأشياء التي لفتت انتباهنا أثناء التجربة، رواية ورش مرسومة بخط عثماني قديم يختلف عن الرسم الذي اعتاد عليه معظم القراء اليوم. على سبيل المثال، حرف القاف يُرسم بنقطة واحدة فوقه بدل نقطتين، وحرف الفاء يُرسم بلا أي نقطة إطلاقاً. هذا رسم أصيل تاريخياً، لكنه يصعب على غالبية المستخدمين غير المعتادين عليه، وهي نقطة تستحق تنبيهاً واضحاً في واجهة التطبيق قبل أن يختار المستخدم هذه الرواية، حتى لا يظن أن هناك خطأ في العرض.
نقطة أخرى لم نكن راضين عنها إطلاقاً: عدم اتساق ملفات SVG في التعامل مع بدايات السور. الأمر لم يكن مجرد اختلاف بسيط، بل تفاوتاً واضحاً لاحظناه بشكل خاص في سورتي الفاتحة والبقرة، حيث تختلف طريقة عرض بداية السورة عن بقية السور. والأهم من ذلك أن ملفات SVG لا تحمل أي زخارف لأسماء السور من الأساس، بعكس ما اعتاد عليه القارئ في المصحف المطبوع، حيث يُميَّز اسم كل سورة بإطار مزخرف واضح. غياب هذه الزخارف كلياً، وليس فقط عدم اتساقها، كان نقطة ضعف حقيقية أضعفت الإحساس بأصالة الصفحة.
8. ميزان التجربة: ما ربحناه، وما دفعناه ثمناً
على جانب المكاسب: دقة تحديد الآيات ارتفعت بشكل ملحوظ بفضل المضلعات الجاهزة، فصارت نوافذ التفسير تظهر فوراً وبدقة. الثيمات باتت شبه مثالية بفضل شفافية الخلفية التي تسمح بأي لون وملمس وتمنح تجربة ليلية حقيقية. عمارة التحميل الذكي (صفحة بصفحة، مع تخزين مؤقت وتجربة كاملة دون اتصال بعد التنزيل) أصبحت أكثر مرونة من أي وقت مضى. وتعدد الروايات بات جاهزاً فورياً: نفس المضلعات، نفس منطق الربط، يعمل مع أي رواية جديدة دون تعديل. أضف إلى ذلك أننا تخلصنا من الحاجة لاستضافة أي بيانات على خوادمنا، بفضل الاعتماد الذكي على jsDelivr كشبكة توصيل مجانية.
أما الثمن، فلم يكن هيّناً. الحقيقة الأهم هنا معكوسة تماماً عما توقعنا: نسخة PNG المحسّنة لدينا كانت خفيفة بالفعل، نحو 70 ميغابايت للرواية الواحدة، بينما تجاوز حجم الرواية الواحدة بصيغة SVG الكاملة (صفحات + بيانات polygon) ما بين 200 إلى 250 ميغابايت وأكثر. هذا فرق كبير جداً لصالح PNG من ناحية التخزين، وليس لصالح SVG كما قد يظن البعض. زمن التحميل الأول أصبح أبطأ أيضاً، لأن تحليل SVG أكثر تعقيداً بكثير من مجرد فك صورة PNG. الأداء أثناء التمرير والعرض تأثر بشكل ملموس أيضاً: قياساتنا أظهرت أن معدل الإطارات (FPS) ينخفض من 60 إطاراً في الثانية إلى نحو 34 إطاراً فقط أثناء تحميل صفحات SVG، وهو انخفاض واضح يُحسّ به المستخدم فعلياً أثناء التنقل بين الصفحات. لتخفيف هذا الأثر قليلاً، حدّثنا آلية التحميل المسبق (prefetch) لتُحمّل 3 صفحات قبل الصفحة الحالية و3 صفحات بعدها في الخلفية، وهو ما جعل التنقل أكثر سلاسة، خصوصاً للروايات التي لم يُحمّلها المستخدم بعد بالكامل على جهازه. لكن هذا التحسين خفف المشكلة ولم يُلغِها. ضبط viewBox عبر أحجام الشاشات المختلفة تطلّب جهداً إضافياً لم نكن نتوقعه. أضف إلى ذلك عدم اتساق بدايات السور وغياب الزخارف عنها بالكامل (كما فصّلنا أعلاه)، وأخيراً، يبقى غموض الترخيص عالقاً بلا حل، وهو خطر قانوني لا يمكن تجاهله على المدى الطويل.
9. الخلاصة: تجربة ممتعة، لكن ليس الآن للإنتاج
بصراحة، لن ننتقل إلى SVG في الإنتاج في الوقت الحالي. أريد الحفاظ على صور PNG وعلى ذلك الإحساس الأصيل بمصحف ورقي حقيقي، نسخة طبق الأصل من الإحساس الذي يعرفه القارئ في المصحف المطبوع، وهو شيء لم تمنحنا نسخة SVG بعد نفس الإحساس الكامل به، خاصة مع الفارق الكبير في حجم التخزين وعدم اتساق بدايات السور.
لكنها كانت تجربة ممتعة حقاً وأنا سعيد أننا خضناها. تعلّمنا الكثير عن إدارة التخزين متعدد المنصات، وعن استخدام CDN مجاني بذكاء بدل استضافة بياناتنا الخاصة، وعن نقاط قوة وضعف الرسم المتجهي مقابل الصور النقطية في سياق تطبيق كهذا. ربما نعود إليها يوماً، حين تنضج بعض هذه التفاصيل (خاصة زخارف بدايات السور وحجم التخزين)، لكن الآن، PNG يبقى الخيار الأنسب لتجربة قراءة تحاكي المصحف الورقي بأمانة.
الكود المصدري للتجربة متاح على GitHub: adelpro/open-mushaf-native، فرع develop-svg