أرسل الجملة العربية يوضح ملف PDF هذا إلى TextOut العادي، وستعود الصفحة خاطئة من جهتين في وقت واحد. الكلمات تسير من اليسار إلى اليمين بدل اليمين إلى اليسار، والحروف تظهر منفصلة في أشكالها المعزولة بدل أن تتصل لتكوّن كلمات مترابطة. لا يظهر أي خطأ. Delphi يترجم المشروع، والملف يفتح، ويخبرك قارئ عربي بأن الناتج غير صالح للاستخدام. الإصلاح يحتاج استدعاءً واحدًا لا تبديل مكتبة: HotPDF يمرر النص من اليمين إلى اليسار عبر دالة منفصلة، RtLTextOut، تتولى إعادة الترتيب التي لن يقوم بها TextOut العادي. أربعة أمور في هذه الدالة تحدد ما إذا كان الناتج صالحًا: ما تفعله بالسلسلة، وكيف يحدد وسيط charset النص المطلوب، والتغيير على مستوى المستند الذي يحدث كأثر جانبي، والعمل الخاص بالخط الذي يجب أن يسبق كل شيء
لماذا يحتاج النص من اليمين إلى اليسار إلى استدعاء مستقل
لا يحتفظ تدفق محتوى PDF بنص قابل للتحرير. بل يحتفظ بأشكال حرفية في مواضع ثابتة، وهذا يعني أن الجهة التي تنشئ هذا التدفق هي المسؤولة عن تحديد ترتيب تلك الأشكال. على الشاشة تولى نظام التشغيل هذه المهمة عنك: أضف العربية إلى TEdit فيعيد محرك النص في النظام ترتيبها وضمها قبل أن ترى بكسلًا واحدًا. ولهذا تبدو السلسلة مثالية في النموذج وتتعطل داخل ملف PDF. لقد أنجز سطح المكتب العمل بصمت، وبمجرد أن تكتب تدفق المحتوى بنفسك يعود العمل إلى جانبك
TextOut يأخذك على ظاهر كلامك. يرسم نقاط الترميز بالترتيب الذي تمررها به، من اليسار إلى اليمين، وهذا صحيح للاتينية والسيريلية وCJK وخاطئ للعربية والعبرية. أما RtLTextOut فهو الاستدعاء الذي يعيد ترتيب السطر أولًا إلى الترتيب البصري من اليمين إلى اليسار ثم يرسمه. يحتفظ HotPDF بالدالتين منفصلتين عمدًا بدل التخمين من الأحرف، لذلك فإن اختيار أيهما تستدعيه هو اختيار سلوك النص الذي ستحصل عليه. أما الآليات الأعمق لإعادة الترتيب ثنائي الاتجاه والتشكيل السياقي العربي فهي موضوع مستقل، مشروح في المقالة عن تشكيل النص العربي والنص من اليمين إلى اليسار مع HotPDF؛ أما النقطة العملية هنا فهي أضيق. استخدم RtLTextOut للمقاطع التي تكتب من اليمين إلى اليسار، واستخدم TextOut لكل شيء آخر، ولا تمرر أحدهما عبر الآخر

وسيط charset هو الذي يحدد نظام الكتابة
ما يحدد لـ RtLTextOut ما إذا كان ينسّق العربية أو العبرية ليس الدالة نفسها، بل الخط. يأخذ SetFont وسيط Windows charset باعتباره الوسيط الرابع، وهذا الرقم ينقل قواعد الكتابة إلى استدعاء اليمين إلى اليسار: القيمة 178 تختار العربية، و177 تختار العبرية. اضبط charset ثم ارسم، وستظهر السطران التاليان بترتيب قراءة صحيح من دون أي إعداد إضافي
// Arabic: charset 178 tells RtLTextOut to apply Arabic rules
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Hebrew: charset 177 switches the rules to Hebrew
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
تفصيلان في تلك الإحداثيات يسهل تفويتهما. الموضع الذي تمرره يظل بداية المقطع في نظام إحداثيات الصفحة نفسه، محسوبًا من الزاوية السفلية اليسرى مع ازدياد Y إلى الأعلى، أي الأصل نفسه الذي يستخدمه TextOut دائمًا؛ RtLTextOut يغيّر ترتيب الأشكال الحرفية، لا نقطة القياس التي تعتمدها الصفحة. وكأي استدعاء رسم آخر، يجب أن يسبق SetFont الرسم وأن يُعاد بعد كل AddPage، لأن الخط الحالي لا يبقى بعد فاصل الصفحة. إذا نسيت إعادة الضبط فستعود الصفحة الثانية إلى أي خط كان نشطًا، وهو ما يعني غالبًا مربعات فارغة بدل العربية
لا يعكس النص الذي عكسته مسبقًا
الخطأ الوحيد الذي يلتهم أكبر قدر من وقت التنقيح هنا هو أن تمرر إلى RtLTextOut سلسلة قلبتها يدويًا من قبل. يصل الناس إلى هذه الدالة بعد أن تظهر المحاولة الأولى مع TextOut العادي معكوسة، والحل المؤقت الشائع هو عكس الأحرف في الكود قبل الرسم. لكن RtLTextOut يعكس داخليًا بنفسه، لذلك فإن السلسلة المعكوسة مسبقًا تُعكس مرة ثانية وتعود إلى نقطة البداية. مرر النص بالترتيب المنطقي، أي بالترتيب الذي تكتبه وتقرأه بصوت عالٍ، ودع الاستدعاء يتولى إعادة الترتيب
الورطة هنا أشد من مجرد قلب مباشر، لأن السلسلة المعكوسة مرتين قد تبدو صحيحة لعبارة اختبار عربية خالصة واحدة، ثم تنهار في اللحظة التي يحتوي فيها السطر على كلمة لاتينية أو رقم. داخل السطر المكتوب من اليمين إلى اليسار من المفترض أن تُقرأ هذه المقاطع المضمّنة من اليسار إلى اليمين، والعكس اليدوي يفسد هذا التضمين بينما ينجو منه المثال العربي الخالص بالصدفة. لذلك يمر الخطأ من أول فحص سريع لك ثم يظهر لاحقًا في فاتورة حقيقية تحتوي على رقم حساب. أزل كل عكس يدوي فور انتقالك إلى RtLTextOut
الأثر الجانبي في Direction الذي يجدر معرفته
استدعاء RtLTextOut يغيّر أكثر من السطر الذي ترسمه. فهو يقلب أيضًا تفضيل اتجاه القراءة في المستند إلى من اليمين إلى اليسار، وهو الأمر نفسه الذي كنت ستضبطه يدويًا عبر الخاصية Direction. هذه الضابطة تضيف vpDirection إلى ViewerPreferences في المستند، وهو ما يخبر عارض PDF بكيفية ترتيب الصفحات المزدوجة ومن أي جانب يبدأ التخطيط المتقابل. عندما يكون المستند كله عربيًا أو عبريًا فهذه بالضبط النتيجة المطلوبة، وتحصل عليها مجانًا
من المهم معرفة ذلك لأنه يظل غير مرئي على صفحة واحدة. إذا كان المستند في معظمه من اليسار إلى اليمين مع كتلة واحدة فقط من اليمين إلى اليسار، فإن أول استدعاء لـ RtLTextOut سيقلب تفضيل الملف كله مع ذلك، ولن يُظهر لك أي شيء في النسخة التجريبية ذات الصفحة الواحدة هذا التغيير. تظهر الأعراض بعد أسابيع عندما يطبع أحدهم كتيبًا مزدوج الوجهين فتأتي الصفحات متقابلة معكوسة. إذا لم تكن تريد ذلك، فأعد Direction صراحةً بعد مقطع اليمين إلى اليسار:
// RtLTextOut already set the document direction to RightToLeft;
// restore left-to-right if the document is predominantly LTR
Pdf.Direction := LeftToRight;
أما إذا كان المستند يُقرأ حقًا من اليمين إلى اليسار فاتركه كما هو. الفكرة هنا هي أن تعرف أن الاستدعاء له أثر على مستوى المستند كله حتى لا يفاجئك شكل الكتيب لاحقًا
سجّل الخط الذي تشحنه، لا الخط الذي تأمل أن يكون مثبتًا
لا قيمة لإعادة الترتيب إذا لم يكن في الخط أشكال حرفية يرسمها. الفشل الكلاسيكي هو تقرير يُعرض بلا عيب على جهاز المطور، حيث يوجد Arial Unicode MS صدفةً، ثم يظهر على خادم العميل في صفوف من المربعات الفارغة لأن Windows استبدل بهدوء خطًا لا يغطي العربية أصلًا. العلاج هو التوقف عن الثقة بخطوط النظام المثبتة وتسجيل خط تشحنه مع التطبيق نفسه
// Ship a known Arabic font and register it before drawing
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
ترتبط بالتسجيل حدودان. الخط الذي تُدخله عبر RegisterUnicodeTTF يُضمَّن داخل الملف، ومعالجة Unicode المضمّنة في HotPDF تحتاج أن يكون المستند بصيغة PDF 1.5 أو أحدث؛ ولا يظهر هذا القيد إلا إذا أصرّ نظام لاحق على PDF 1.4، وعندها يكون الفشل صامتًا. أما الحد الآخر فهو قانوني لا تقني: ملفات TrueType تحمل بتات إذن للتضمين، وقد يكون الخط الذي يبدو مناسبًا على الشاشة مرخّصًا بطريقة تمنع شحنه داخل مستندات العملاء. تحقّق من الترخيص قبل التضمين، لا بعد وصول شكوى
مثال كامل لوحدة تحكم
لجمع الأجزاء معًا، إليك برنامجًا مستقلًا يكتب صفحة واحدة فيها سطر عربي وسطر عبري وسطر مختلط يحمل اسم منتج لاتيني. كل كتلة تضبط charset الخاص بها ثم ترسم بالترتيب المنطقي
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // HotPDF main unit
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// A Latin heading goes through the ordinary TextOut path
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');
// Arabic: charset 178, logical order, RtLTextOut does the reordering
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Hebrew: charset 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Mixed line: the embedded Latin word still reads left to right
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 640, 0,
'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');
Pdf.EndDoc;
Writeln('Wrote RtLTextOut.pdf');
finally
Pdf.Free;
end;
end.
شغّله وافتح الناتج. ستُقرأ السطور العربية والعبرية من اليمين إلى اليسار، وستتصل الحروف حيث تتصل في هذا الخط، وفي السطر الأخير ستظهر الكلمة HotPDF من اليسار إلى اليمين داخل المقطع العربي، وهو الناتج المطابق للمواصفة حتى لو بدا غريبًا لأول وهلة على من يرى التخطيط ثنائي الاتجاه للمرة الأولى. تستحق هذه الملاحظة أن تدخل معايير القبول قبل أن يراجع الناتج متحدث أصلي بالعربية، لأن قراءة المقطع المضمّن على نحو يبدو «خاطئًا» مقارنة بالنص المحيط هي أكثر ما يُبلّغ عنه كعيب وهو ليس عيبًا
التحقق من الناتج
الصفحة التي تبدو صحيحة ليست هي نفسها الصفحة الصحيحة، لذلك تحقّق منها بالطريقة التي سيتبعها النظام اللاحق. انسخ النص من العارض وقارن نقاط الترميز مع السلسلة المصدرية؛ فالتسلسل البصري الصحيح مع تسلسل منطقي مشوش هو نمط فشل حقيقي. شغّل البحث داخل المستند عن كلمة تستطيع رؤيتها على الصفحة. ثم افتح الملف على جهاز لا يحتوي خطوط التطوير الخاصة بك، فهو الأكثر احتمالًا لإظهار استبدال صامت. ولا يغني كل ذلك عن قراءة متحدث أصلي لوثيقة حقيقية واحدة، لأنها تلتقط مشكلات لن يكشفها أي نص اختبار صناعي، لذا ضع هذه المراجعة على الجدول قبل شحن الصيغة
RtLTextOut يتولى إعادة الترتيب ثنائي الاتجاه والتشكيل السياقي العربي، وهذا يغطي الغالبية العظمى من عمل التقارير والمستندات المكتوبة من اليمين إلى اليسار. أما حدوده، أي النصوص التي تحتاج أكثر من إعادة الترتيب والوصل مثل عائلات اللغات الهندية، والميزات الاختيارية في OpenType التي تمر عبر الاستبدال أحادي المحرف، فقد جرى رسمها مع تفاصيل تغطية المحارف والتشكيل في المقالة المرافقة عن تشكيل النص العربي والنص من اليمين إلى اليسار مع HotPDF
الاستدعاءات RtLTextOut وSetFont وRegisterUnicodeTTF المعروضة هنا هي جزء من مكوّن HotPDF لـ Delphi وC++Builder