المتابعون للمدونة

الجمعة، 7 يوليو 2017

تظليل خلفية كل سطر في مربع النص

تظليل خلفية كل سطر في مربع النص المنسق RichTextBox


 
ستجدون الكود كاملا في هذا المشروع (توجد نسخة بسي شارب وأخرى بفيجوال بيزيك دوت نت):

أحد المشاكل التي واجهتني بعد عرض الترجمة في مربع نص شفاف فوق الفديو كما شرحت في هذا الموضوع، هو أن الإضاءة في بعض المشاهد قد تكون شديدة ولا تستطيع العين قراءة نص الترجمة الأبيض.. وقد وجدت أن أفضل حل لهذه المشكلة هي عرض الترجمة بحروف لونها أبيض على أرضية سوداء.. ولكن بشرط أن تكون الأرضية السوداء خلف كل سطر على حدة كما هو واضح في الصورة، لأنني لو جعلت خلفية مربع النص كلها سوداء فسأكون بذلك ألغيت الشفافية، وأخفيت ثلث مساحة الفديو أو أكثر في بعض الحالات التي تظهر فيها الترجمة على سطرين أو ثلاثة!
إذن السؤال الآن: كيف يمكن رسم مستطيل أسود خلف كل سطر في مربع النص؟
وتتمثل الإجابة في هذه الخطوات:
1- معرفة بداية ونهاية كل سطر يعرضه مربع النص.
2- معرفة حدود المستطيل المحتوي لحروف هذا السطر.
3- رسم المستطيل في خلفية مربع النص قبل كتابة النص (حتى لا يمحو لون التظليل الحروف المكتوبة).
وإليكم كيف يمكن تنفيذ هذه الخطوات:
 
1- معرفة بداية ونهاية كل سطر يعرضه مربع النص:
في البداية يمكننا أن رقم آخر سطر يعرضه مربع النص بإرسال رقم آخر حرف في مربع النص إلى الوسيلة GetLineFromCharIndex كما شرحت في هذا المنشور.
الآن نريد أن نعرف رقم أول حرف في كل سطر، بدءا من السطر رقم صفر إلى السطر الأخير الذي حصلنا عليه في الخطوة السابقة.. الأمر بسيط، فلدينا الوسيلة GetFirstCharIndexFromLine التي تستقبل رقم السطر وتعيد رقم أول حرف فيه.. إذن فكل المطلوب هو المرور على كل السطور ومعرفة رقم أول حرف في كل سطر.. وواضح طبعا أن رقم آخر حرف في كل سطر = رقم أول حرف في السطر التالي - 1.
وستجدون الكود الذي ينفذ كل هذا في الدالة GetLineBounds في المشروع TransRTFViewer.
 
2- معرفة حدود المستطيل المحتوي لحروف هذا السطر:
يمكن استخدام وسائل قياس النص لمعرفة مساحة عرضه، لكن تجربتي مع هذه الوسائل في مربع النص المنسق RichTextBox أظهرت لي عدم دقتها لأن مربع النص المنسق يستخدم طرقا أخرى لرسم النص.. لهذا لجأت إلى حل أبسط، وهو استخدام الوسيلة GetPositionFromCharIndex التي تستقبل رقم أحد الحروف في مربع النص، وتعيد إحداثياته داخل مربع النص.. الآن يمكننا أن نعرف موضع أول حرف في كل سطر وموضع آخر حرف في كل سطر.. هاتان النقطتان تمثلا رأسي المستطيل العلويين فقط، وسيتبقى أمامنا أن نعرف ارتفاع هذا المستطيل لمعرفة موضع الرأسين الآخرين.. من السهل أن نعرف ارتفاع كل سطر بمعرفة ارتفاع الخط المستخدم في كتابة الحروف (سنرى هذا بعد قليل).
لاحظ أن الرأس الأيسر والرأس الأيمن سيكونان معكوسين عند التعامل مع نص عربي بدلا من نص إنجليزي.. ولحل هذه المشكلة سنفحص قيمة الموضع الأفقي لحرفي بداية السطر ونهايته، ونعتبر النقطة التي موضعها أقل هي الرأس الأيسر للمستطيل.
أخيرا: ستتبقى لدينا مشكلة صغيرة، وهي أن موضع كل حرف هو موضع الرأس العلوي الأيسر للمستطيل المرسوم داخله الحرف.. وهذا يعني أن مساحة الحرف الأخير فغي النص الإنجليزي (أو الحرف الأول في النص العربي) غير داخلة في حساباتنا.. لحل هذه المشكلة علينا أن نعرف مساحة هذا الحرف.. لفعل هذا يمكننا استخدام الوسيلة Graphics.MeasureString لحساب مساحة عرض النص الموجود في السطر كله (لا تحاول استخدام هذه الوسيلة للحصول على مستطيل السطر مباشرة فهي غير دقيقة) ثم نفعل المثل لحساب مساحة عرض النص الموجود في هذا السطر ما عدا أول حرف.. والفارق بين الاثنتين هو مساحة الحرف الأول بدقة معقولة.. وبالتجربة وجدت أنني أحتاج لضرب مساحة الحرف في 1.2 لزيادة هامش المستطيل قبل بداية الحرف.
وستجدون الكود الذي ينفذ كل هذا في الدالة AddRect في المشروع TransRTFViewer.
 
3- رسم المستطيل في خلفية مربع النص قبل كتابة النص:
أحد مشاكل مربع النص أنه لا يطلق الحدث Paint.. ولحل هذه المشكلة يجب أن ترث فئة مربع النص لإنشاء فئة جديدة وبداخلها تستخدم الوسيلة المحمية WndProc التي يتم استدعاؤها مع كل حدث يحدث للأداة بالفأرة أو لوحة المفاتيح أو غير ذلك، حيث يتم إرسال رسائل الويندوز إلى هذه الوسيلة عبر المعامل m، وكل المطلوب هو تصيد رسالة إنعاش رسم الأداة WM_PAINT، وفيها نستدعي الدالة DrawRectangles التي تقوم برسم مستطيلات التظليل حول كل سطر.. لاحظ أن مربع النص ما زال لم يرسم الحروف إلى هذه اللحظة، ولن يرسمها إلا بعد أن تمرر إليه الرسالة الأصلية القادمة إلى الوسيلة WndProc.. وهذا هو السبب في استدعائنا للوسيلة base.WndProc الخاصة بالفئة الأم في نهاية الكود.
الآن كل المطلوب في الدالة DrawRectangles هو رسم المستطيلات حول كل سطر.. لقد عرفنا كيف نحسب هذه المستطيلات، لكن بقي أن نعرف ارتفاع كل مستطيل.
يمكن معرفة ارتفاع كل سطر باستخدام الخاصية Font.Height التابعة للخط الذي يستخدمه مربع النص.. المشكلة أن هذا الارتفاع يحتوي على هامش علوي وهامش سفلي (حتى لا تتلاصق حروف السطور المتتالية).. ولو استخدمت هذا الارتفاع كما هو بدون إزالة هذين الهامشين، فستتلاصق المستطيلات التي ترسمها حول السطور المتتالية.. هذا ليس خطأ ولكنه قد يضيع مساحة لا ضرورة لها من مساحة الفديو المعروض في الخلفية.
لحساب هذين الهامشين، يجب أن نعرف المساحة التي يشغلها الخط في كل سطر.. يوجد جزء من الحروف يظهر فوق السطر، وجزء آخر يظهر تحت السطر.. يمكن معرفة ارتفاع هذين الجزئين بالترتيب باستخدام الوسيلتين:
x1 = Font.FontFamily.GetCellAscent(Font.Style)
x2 = Font.FontFamily.GetCellDescent(Font.Style)
لكن القيمة التي تعيدها هاتين الوسيلتين محسوبة بوحدة التصميم Design Unit ويجب تحويلها أولا إلى نقاط الشاشة Pixels بضربها في النسبة بين حجم الخط وبين الارتفاع النسبي للخط كما تحسب من هذه المعادلة:
i = this.Font.Size / this.Font.FontFamily.GetEmHeight(Font.Style)
الآن يمكننا أن نعرف الهامش بين كل سطرين من العلاقة:
h = Font.Height - 2 * x1 * i
لاحظ أنني لم استخدم ارتفاع الجزء السفلي واستخدمت ارتفاع الجزء العلوي بدلا منه (لهذا ضربته في 2) لأن هذا أعطاني نتائج عملية أفضل.
لاحظ أن هذا الهامش h يظهر مرتين: مرة أعلى السطر ومرة أسفله.. لهذا عند حساب الارتفاع الحقيقي للسطر طرحته مرتين:
LineHeight = this.Font.Height - 2 * h
الآن صار تكوين المستطيل سهلا، فلو كان المستطيل الذي حصلنا عليه من حساب بداية ونهاية كل سطر (والذي لم نحدد ارتفاعه الصحيح) هو r إذن:
rect = new RectangleF(r.X, r.Y + h, r.Width, LineHeight)
كل ما فعلناه هو زيادة الموضع الرأسي بمقدار الهامش بين السطرين، وجعلنا ارتفاع المستطيل هو الارتفاع الحقيقي للسطر.
الآن صار من السهل استخدام الوسيلة Graphics.FillRectangle لرسم هذا المستطيل.
وستجدون الكود كاملا في هذا المشروع (توجد نسخة بسي شارب وأخرى بفيجوال بيزيك دوت نت):
 

ليست هناك تعليقات:

إرسال تعليق

ملحوظة: يمكن لأعضاء المدونة فقط إرسال تعليق.

صفحة الشاعر