مقال تقني

RtLTextOut في HotPDF: نص PDF من اليمين لليسار في Delphi

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

التوقيع والمعاملات

procedure RtLTextOut(X, Y: Single; angle: Extended;
  Text: WideString); overload;
procedure RtLTextOut(X, Y: Single; angle: Extended;
  Text: PWORD; TextLength: Integer); overload;

تثبّت X وY السلسلة في نظام إحداثيات الصفحة نفسه، مقيسةً من الزاوية السفلية اليسرى مع تزايد Y نحو الأعلى، وهي نقطة الأصل نفسها التي يستخدمها كل استدعاء TextOut؛ فـRtLTextOut تغيّر ترتيب الأحرف الرسومية لا المكان الذي تقيس منه الصفحة. وتدوّر angle خط الأساس تمامًا كما تفعل في TextOut، لذا يرسم 0 سطرًا أفقيًا. وText هي السلسلة بالترتيب المنطقي، أي الترتيب الذي تكتبها به، ويأخذ التحميل الزائد الثاني بيانات UTF-16 نفسها كمخزن PWORD خام مع عدد صريح لوحدات الترميز، وهي الصيغة التي تستخدمها حين يصل النص من API لا من سلسلة Delphi. وفي إصدارات Delphi الأقدم التي تسبق حلّ التحميل الزائد لهذه الأنواع، تُتاح صيغة السلسلة تحت الاسم RtLTextOutStr بقائمة المعاملات ذاتها

وتقسيم العمل بين استدعاءي الإخراج صارم. فترسم TextOut نقاط الترميز بالترتيب الذي تمرّرها به، وهو صحيح للاتينية والسيريلية والكتابات الصينية واليابانية والكورية وخاطئ للعربية والعبرية. وتعيد RtLTextOut ترتيب كل سطر إلى الترتيب البصري من اليمين إلى اليسار أولًا ثم ترسم، مبقيةً الكلمات اللاتينية والأرقام المضمَّنة تُقرأ من اليسار إلى اليمين داخل السطر. ويُبقي HotPDF الطريقتين منفصلتين عمدًا بدلًا من تخمين الاتجاه من الأحرف، فاختيار أيهما تستدعي هو اختيار أي سلوك كتابة تحصل عليه؛ استخدم RtLTextOut للسلاسل المكتوبة من اليمين إلى اليسار، وTextOut لكل ما عداها، ولا توجّه إحداهما عبر الأخرى أبدًا. أما لماذا توجد إعادة الترتيب أصلًا، وما الذي تفعله فعلًا خوارزمية Unicode ثنائية الاتجاه والوصل السياقي العربي، وأين يتوقف تشكيل HotPDF، فهي موضوع المقالة المرافقة عن تشكيل النص العربي والنصوص من اليمين إلى اليسار مع HotPDF؛ وكل ما يلي هو الإعداد العملي

مخطط لكيفية إعادة RtLTextOut ترتيب سطر مختلط من العربية واللاتينية إلى الترتيب البصري من اليمين إلى اليسار قبل رسمه في PDF
تعيد RtLTextOut ترتيب كل سطر إلى الترتيب البصري قبل الرسم: تحتفظ السلاسل المكتوبة من اليمين إلى اليسار بتسلسلها بينما تُقرأ الكلمات اللاتينية والأرقام المضمَّنة من اليسار إلى اليمين داخل السطر

وسيط مجموعة الأحرف يقرّر الكتابة

ما يخبر RtLTextOut بما إذا كانت تنسّق عربية أم عبرية ليس الطريقة بل الخط. فتأخذ SetFont مجموعة أحرف Windows وسيطًا رابعًا، وتلك القيمة تحمل قواعد الكتابة إلى استدعاء اليمين إلى اليسار: 178 تختار العربية، و177 تختار العبرية. اضبط مجموعة الأحرف، ثم ارسم، فيخرج السطران أدناه بترتيب قراءة صحيح دون أي إعداد إضافي

// العربية: مجموعة الأحرف 178 تخبر RtLTextOut بتطبيق القواعد العربية
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');

// العبرية: مجموعة الأحرف 177 تبدّل القواعد إلى العبرية
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

ثمة تفصيل في التسلسل يسهل إغفاله: يجب أن تأتي SetFont أولًا ويجب تكرارها بعد كل AddPage، لأن الخط الحالي، بما فيه مجموعة الأحرف، لا ينجو من فاصل الصفحات. أغفل التكرار فتعود الصفحة الثانية إلى أي خط كان نشطًا، وهو ما يعني للعربية عادةً مربعات فارغة

لا تعكس نصًا عكسته أنت بالفعل

الخطأ الوحيد الذي يبتلع أكبر قدر من وقت التنقيح هنا هو تمرير سلسلة قلبتها يدويًا بالفعل إلى RtLTextOut. فيصل الناس إلى هذه الطريقة بعد أن خرجت محاولة أولى مع TextOut العادية معكوسة، والحل المؤقت الشائع هو عكس الأحرف في الكود قبل الرسم. لكن RtLTextOut تعكس داخليًا من تلقاء نفسها، فتُعكس السلسلة المعكوسة مسبقًا مرة ثانية وتعود إلى حيث بدأت تمامًا. مرّر النص بالترتيب المنطقي، أي الترتيب الذي تكتبه به وتقرأه بصوت عالٍ، ودع الاستدعاء يتولى إعادة الترتيب

والفخ أخبث من قلب بسيط لأن السلسلة المعكوسة مرتين قد تبدو صحيحة لعبارة اختبار عربية بالكامل ثم تنكسر لحظة يحمل السطر كلمة لاتينية أو رقمًا. فداخل السطر المكتوب من اليمين إلى اليسار يُفترض أن تُقرأ تلك السلاسل المضمَّنة من اليسار إلى اليمين، والعكس اليدوي يخرّب هذا التداخل بينما تصادف الحالة العربية الخالصة أن تنجو منه. وهكذا تعبر العلّة اختبار الدخان الأول لديك وتظهر لاحقًا على فاتورة حقيقية تحمل رقم حساب. أزل كل عكس يدوي لحظة تنتقل إلى RtLTextOut

الأثر الجانبي على Direction الذي يستحق المعرفة

يغيّر استدعاء RtLTextOut أكثر من السطر الذي ترسمه. فهو يقلب أيضًا تفضيل اتجاه القراءة في المستند إلى اليمين إلى اليسار، وهو الشيء نفسه الذي كنت ستضبطه بنفسك عبر الخاصية Direction. ويضيف ذلك المُعيِّن vpDirection إلى ViewerPreferences الخاصة بالمستند، مما يخبر العارض كيف يرتّب العرض المزدوج للصفحات ومن أي جانب يبدأ تخطيط الصفحات المتقابلة. وحين يكون المستند كله عربيًا أو عبريًا فهذا بالضبط ما تريده، وتحصل عليه مجانًا

ويستحق المعرفة تحديدًا لأنه غير مرئي في صفحة واحدة. فإن كان المستند في معظمه من اليسار إلى اليمين مع كتلة واحدة من اليمين إلى اليسار، فسيقلب أول استدعاء RtLTextOut تفضيل الملف كله مع ذلك، ولن يُظهر شيء في نموذج الصفحة الواحدة لديك ذلك. ويظهر العرَض بعد أسابيع حين يطبع أحدهم كتيّبًا على الوجهين فتخرج الصفحات المتقابلة معكوسة. فإن لم يكن هذا ما تريده، فأعد ضبط Direction صراحةً بعد السلسلة المكتوبة من اليمين إلى اليسار:

// ضبطت RtLTextOut اتجاه المستند بالفعل على RightToLeft؛
// أعد اليسار إلى اليمين إن كان المستند في معظمه LTR
Pdf.Direction := LeftToRight;

أما المستند الذي يُقرأ فعلًا من اليمين إلى اليسار فاتركه كما هو. والمقصود أن تعرف أن للاستدعاء أثرًا على مستوى المستند كله حتى لا تقع مفاجأة الكتيّب أبدًا

سجّل الخط الذي تشحنه لا الخط الذي تأمل أنه مثبَّت

لا تهم إعادة الترتيب كلها إن لم يكن للخط أحرف رسومية يرسمها. والإخفاق الكلاسيكي هو تقرير يُصيَّر بلا عيب على جهاز المطوّر، حيث يصادف وجود Arial Unicode MS، ويخرج صفوفًا من المربعات الفارغة على خادم العميل حيث استبدل Windows بهدوء خطًا بلا أي تغطية للعربية على الإطلاق. والعلاج هو التوقف عن الوثوق بخطوط النظام المثبَّتة وتسجيل خط تشحنه مع التطبيق

// اشحن خطًا عربيًا معروفًا وسجّله قبل الرسم
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 بتات إذن التضمين، والوجه الذي يبدو جيدًا على الشاشة قد يكون مرخَّصًا بطريقة تمنع شحنه داخل مستندات العملاء. تحقق من الترخيص قبل أن تضمّن لا بعد شكوى

مثال كامل لتطبيق وحدة تحكم

وبجمع القطع معًا، إليك برنامجًا مكتفيًا بذاته يكتب صفحة واحدة بسطر عربي وسطر عبري وسطر مختلط يحمل اسم منتج لاتينيًا. وتضبط كل كتلة مجموعة أحرفها ثم ترسم بالترتيب المنطقي

program RtLTextOutDemo;

{$APPTYPE CONSOLE}

uses
  HPDFDoc;   // الوحدة الرئيسية لـ HotPDF

var
  Pdf: THotPDF;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'RtLTextOut.pdf';
    Pdf.BeginDoc;

    // العنوان اللاتيني يمر عبر مسار TextOut العادي
    Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
    Pdf.CurrentPage.TextOut(40, 780, 0, 'Right-to-left text with HotPDF');

    // العربية: مجموعة الأحرف 178، ترتيب منطقي، وتتولى RtLTextOut إعادة الترتيب
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
    Pdf.CurrentPage.RtLTextOut(400, 720, 0,
      'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');

    // العبرية: مجموعة الأحرف 177
    Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
    Pdf.CurrentPage.RtLTextOut(400, 680, 0,
      'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');

    // سطر مختلط: الكلمة اللاتينية المضمَّنة لا تزال تُقرأ من اليسار إلى اليمين
    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 من اليسار إلى اليمين داخل السلسلة العربية. وهذا التداخل هو النتيجة ثنائية الاتجاه الصحيحة لا علّة، وإن كان المراجعون لأول مرة يبلّغون عنه بانتظام على أنه علّة؛ وتشرح مقالة التشكيل المرتبطة أعلاه لماذا تفرضه قواعد Unicode وكيف تصوغ معايير القبول لديك حتى لا يُقدَّم ذلك البلاغ أبدًا

الأخطاء الشائعة وإصلاحاتها

كل إخفاق أدناه ظهر في سلسلة دعم حقيقية، ويعود كل منها إلى أحد الأقسام أعلاه

  • المخرجات تُقرأ معكوسة أو تتشوّش في السطور المختلطة — عُكست السلسلة يدويًا قبل الاستدعاء، وهو عادةً بقايا حل التفافي من محاولة مع TextOut. احذف كل عكس يدوي ومرّر الترتيب المنطقي؛ فـRtLTextOut تعكس داخليًا
  • الحروف تُطبع منفصلة في أشكالها المعزولة — مرّ النص عبر TextOut العادية، أو استُدعيت SetFont دون مجموعة أحرف من اليمين إلى اليسار. ارسم بـRtLTextOut ومرّر 178 للعربية أو 177 للعبرية وسيطًا رابعًا لـSetFont
  • مربعات فارغة على جهاز العميل — استبدل Windows خطًا بلا تغطية للعربية أو العبرية. كفّ عن تسمية الخطوط المثبَّتة؛ سجّل وجهًا تشحنه عبر RegisterUnicodeTTF واضبطه بـSetFont بذلك الاسم
  • الصفحة الثانية تُصيَّر بالخط الخاطئ — الخط الحالي لا ينجو من AddPage. كرّر استدعاء SetFont، بما فيه مجموعة الأحرف، بعد كل فاصل صفحات
  • الصفحات المتقابلة تُطبع معكوسة على الوجهين في مستند معظمه LTR — قلب أول استدعاء RtLTextOut خاصية Direction الخاصة بالمستند كأثر جانبي. اضبط Pdf.Direction := LeftToRight بعد السلسلة المكتوبة من اليمين إلى اليسار
  • نص Unicode المضمَّن يتدهور بصمت في المراحل اللاحقة — شيء في خط الأنابيب يفرض PDF 1.4، ومعالجة Unicode المضمَّن في HotPDF تحتاج إلى 1.5 أو أحدث. ارفع إصدار المستند أو أزل القيد اللاحق

قبل أن يُشحن التنسيق، تحقق بما يتجاوز النظر بالعين: انسخ النص من العارض مرة أخرى، وشغّل البحث داخل المستند، وافتح الملف على جهاز يخلو من خطوط التطوير لديك، وضع مستندًا حقيقيًا واحدًا أمام قارئ ناطق باللغة. وقائمة التحقق الكاملة، وخريطة التغطية لكل كتابة، ومجموعة سلاسل الاختبار الجديرة بالبناء، كلها في المقالة المرافقة عن تشكيل النص العربي والنصوص من اليمين إلى اليسار مع HotPDF

استدعاءات RtLTextOut وSetFont وRegisterUnicodeTTF المعروضة هنا جزء من مكوّن HotPDF لـ Delphi لـ Delphi و C++Builder