مقال تقني

تشكيل النص العربي ونصوص RTL في ملفات PDF من Delphi باستخدام HotPDF

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

تتناول هذه المقالة نصوص اليمين إلى اليسار والكتابات المعقدة باستخدام HotPDF، وهو مكوّن PDF أصلي VCL لـ Delphi وC++Builder، بما في ذلك الموضع الذي ينتهي عنده دعم التشكيل فعليا، وهذا مهم بالقدر نفسه عند تقرير ما إذا كان يغطي اللغات المطلوبة لديك

تحويلان يفصلان السلسلة النصية عن السطر المطبوع

يخزن Unicode النص بترتيب منطقي: الترتيب الذي يُكتب ويُخزن ويُقرأ بصوت عال. أما العارض فيرسم بترتيب مرئي. في الكتابات من اليمين إلى اليسار يختلف الترتيبان، وفي المحتوى المختلط، مثل جملة عربية تحتوي الرمز اللاتيني "PDF" أو سعرا بالأرقام، تحدد خوارزمية Unicode Bidirectional Algorithm (UAX #9) كيف تُضمن المقاطع من اليسار إلى اليمين داخل سطر من اليمين إلى اليسار. هذا هو التحويل الأول: إعادة الترتيب

التحويل الثاني هو التشكيل السياقي. يأخذ الحرف العربي شكلا مختلفا بحسب كونه في بداية الكلمة أو وسطها أو آخرها أو منفردا؛ لا تتغير codepoint، بل يتغير الشكل المرسوم فقط. خط أنابيب نصي يربط codepoints مباشرة بالرموز الافتراضية ينتج الإخراج المفكك الموصوف أعلاه. العبرية لا تحتاج إلى وصل، لكنها ما زالت تحتاج إلى إعادة ترتيب؛ أما العربية فتحتاج إلى الاثنين، ولهذا هي حالة الاختبار النموذجية

تخفي بيئة سطح المكتب هذه الآلية. عندما يرسم تطبيق VCL نصا عربيا على الشاشة، يعيد مكدس النص في نظام التشغيل ترتيبه وتشكيله دون أن تراه، ولذلك فإن السلسلة نفسها التي تظهر بشكل مثالي في TEdit تخرج خاطئة في PDF ساذج. يخزن تيار محتوى PDF رموزا موضوعة، لا مقاطع نصية قابلة للتحرير، ولذلك فإن من يكتب التيار يملك مسؤولية التشكيل، وهذه هي الفجوة التي وُجد RtLTextOut لإغلاقها

RtLTextOut: إعادة ترتيب ووصل في استدعاء واحد

يفصل HotPDF مسار اللاتينية عن مسار الكتابات المعقدة على مستوى API. يرسم TextOut ما يُعطى له، وبالترتيب الذي يُعطى به؛ أما RtLTextOut فيشغل إعادة الترتيب والتحليل السياقي أولا. يخبر معامل charset في SetFont المحرك بأي قواعد كتابة يجب تطبيقها: 178 يختار معالجة العربية، و177 يختار العبرية

// Arabic: pass logical order; RtLTextOut reorders and joins
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF');

// Hebrew: reordering only, no contextual joining
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

الفخ الذي يستهلك أكثر وقت التصحيح: RtLTextOut يجري العكس بنفسه. إذا غذّيته بنص معكوس مسبقا، غالبا كبقايا "إصلاح" من محاولة أقدم باستخدام TextOut العادي، فسيعكس السطر مرتين. وقد يبدو ذلك صحيحا لسلسلة عربية صرفة واحدة، ثم ينكسر عند أول سطر يحتوي حروفا لاتينية أو أرقاما، لأن المقاطع المختلطة لم تعد تتبع ترتيب UAX #9. مرر دائما الترتيب المنطقي واترك API يقوم بالعمل

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

تغطية الرموز تحسم النتيجة قبل تشغيل التشكيل

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

// Ship a known font instead of relying on installed system fonts
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12);

// Audit coverage for the codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628);  // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);

ينطبق حدان متعلقان بالإصدار. الخطوط المسجلة بهذه الطريقة يجب تضمينها، ومعالجة HotPDF للخطوط المضمنة بتنسيق Unicode تتطلب أن يكون إصدار PDF في المستند 1.5 أو أحدث، وهذا لا يهم إلا إذا كان نظام لاحق يثبت إخراجك عند PDF 1.4. كما يجب أن يسمح ترخيص الخط بالتضمين: تحمل ملفات TrueType بتات إذن التضمين، وقد يكون الخط الذي يرسم جيدا على الشاشة غير صالح قانونيا للتوزيع داخل مستندات العملاء

GetUnicodeGlyphForCodepoint هو خطاف التدقيق: مر على نطاقات codepoint التي تستخدمها بياناتك عند بدء الخدمة وسجل glyph IDs التي حُلّت، بحيث تظهر فجوة التغطية في سطر سجل أثناء النشر بدلا من أن تظهر كأحرف مفقودة في فاتورة عميل

لنص Unicode غير المكتوب من اليمين إلى اليسار، مثل سلاسل CJK، وحركات Vietnamese، والنصوص الأوروبية المختلطة، ينطبق المسار العادي: يقبل TextOut قيمة WideString ويرسمها عبر الخط المسجل من دون تحليل ثنائي الاتجاه. إبقاء مساري الاستدعاء منفصلين في كود التقارير، روتين لمقاطع RTL وروتين لكل ما عداها، يجعل سلوك اللغة صريحا بدلا من دفنه في علم لا يتذكر أحد ضبطه

ترتيب القراءة خاصية في المستند أيضا

صحة الرموز على مستوى glyph ليست نهاية العمل. يعرّف ISO 32000-1 §12.2 تفضيلا للعارض، /Direction، يعلن اتجاه القراءة الغالب في المستند. لا يغير أي رموز؛ بل يخبر العارضات كيف ترتب الهوامش ذات الصفحتين، وأين تثبت التقدم في تخطيطات الصفحات المتقابلة، وأي اتجاه يجب أن تفترضه الواجهة، وهي تفاصيل مهمة للكتيبات وأي مستند يقلبه المستخدم

// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft;  // adds vpDirection to ViewerPreferences

تعيين Direction كاف وحده، فمحدد الخاصية يضيف vpDirection إلى ViewerPreferences تلقائيا، وبذلك يصل التفضيل إلى الملف بسطر واحد. ما يجب الانتباه إليه هو حذف الإعلان تماما، وهو سهل لأن شيئا مرئيا لا يتغير في صفحة واحدة؛ ولا يظهر إلا عندما يطبع أحدهم كتيبا مزدوج الوجه فتخرج الصفحات المتقابلة معكوسة

أين يتوقف تشكيل HotPDF

خريطة قدرات صادقة توفر أسبوع تقييم. يتعامل RtLTextOut مع إعادة الترتيب ثنائية الاتجاه والوصل السياقي العربي تلقائيا. أما ligatures الطباعية الاختيارية وتطبيق ميزات OpenType الأوسع فليسا تلقائيين: يحل GetSingleSubstituteGlyph(GID, 'liga') تبديلا واحدا في كل مرة، glyph ID داخلا ووسم الميزة إلى جانبه، وهذا يصلح لقائمة ligature معروفة ومحدودة تطبقها بنفسك، لكنه ليس محرك GSUB عاما. للكتابات التي تتجاوز مطالب تشكيلها ذلك، مثل الكتابات الهندية ذات علامات العلة المعاد ترتيبها، شغّل تجربة pilot بسلاسل عملاء حقيقية قبل الالتزام باللغة، بدلا من الاستقراء من نتائج العربية

يجب أن يكون التحقق شاملا من البداية إلى النهاية، لأن الصفحة قد تبدو صحيحة ومع ذلك تفشل في كل استخدام لاحق. ثلاثة فحوص تلتقط معظم المشاكل: انسخ النص مرة أخرى من Acrobat وقارن codepoints بسلسلة المصدر؛ ابحث داخل المستند عن كلمة ظاهرة على الصفحة؛ وراجع الإخراج على جهاز لا يملك خطوط التطوير لديك مثبتة. زميل يقرأ اللغة ينظر إلى مستند حقيقي واحد أفضل من أي كمية من بيانات اختبار مصطنعة، فحدد تلك المراجعة قبل شحن التنسيق لا بعد أول شكوى

اختر سلاسل الاختبار عمدا بدلا من إعادة استخدام ما أرسله مترجم في العام الماضي. الحد الأدنى المفيد لكل لغة: جملة بالكتابة الصرفة، وجملة تضم أسماء علامات لاتينية، وسطر بأرقام وعملة، وأسماء تحمل علامات تشكيل أو combining marks. تكسر أسماء العملاء الحقيقية افتراضات تشكيل لا يمسها نص الحشو؛ يجب أن تنمو مجموعة regression بإدخال واحد كلما كشفت حالة دعم نمطا جديدا

تسجيل الخطوط، والتجزيء، وAPI العام لرسم النصوص مشروحة في مقالة إخراج التقارير والخطوط والصور مع HotPDF؛ وإذا كان على المستندات نفسها أيضا تلبية ملفات تعريف الإتاحة، فإن متطلبات وسم اللغة والبنية في مقالة التحقق من PDF/A وPDF/UA تتراكم فوق عمل التشكيل الموصوف هنا

واجهات API الخاصة باليمين إلى اليسار وخطوط Unicode في هذه المقالة تُشحن مع HotPDF Component لـ Delphi وC++Builder؛ وتربط صفحة المنتج مرجع إخراج النص الكامل