تعالج PDFlibPas الأحرف التي لا يستطيع الخط المحدد رسمها من خلال البحث في سلسلة احتياطية من الخطوط المثبتة، عنقوداً تلو عنقود، مع الحفاظ على التشكيل وترتيب التشغيل ثنائي الاتجاه. تُفعَّل هذه الميزة عبر SetAutomaticFontFallback، وتُوسَّع السلسلة عبر AddFontFallback، ولا يُضمَّن في الملف سوى الخطوط الاحتياطية المستخدمة فعلياً في الإخراج
المشكلة التي تحلها هذه الميزة يواجهها كل مولّد مستندات في أول مرة يصل فيها اسم عميل مكتوب بخط كتابة لم يتوقعه خط القالب أبداً. هذا الفشل صامت، وهذا ما يجعله مكلفاً
لماذا يختفي النص غير المدعوم بدلاً من إظهار خطأ؟
لأن PDF لا يملك مفهوماً لخط لا يستطيع رسم حرف ما. الخط البسيط يربط رموز البايت بأسماء الغليفات عبر ترميز؛ أما الخط المركّب فيربط الرموز عبر CMap بفهارس الغليفات. عندما تطلب غليفاً لا يحتويه الخط تحصل على فهرس الغليف صفر، .notdef، الذي ترسمه معظم الخطوط كعدم شيء أو كمربع فارغ. الملف صحيح بنيوياً، ومشغّل النص مكتوب بشكل سليم، والصفحة تُعرض. الأمر ببساطة أن المكان الذي كان يجب أن يظهر فيه الاسم يبقى فارغاً
لا شيء في ISO 32000-1 يُلزم المولّد بملاحظة ذلك. المولّد الذي يكتب النص دون التحقق من التغطية ينتج ملف PDF مطابقاً من الناحية التقنية لكنه فقد محتوى بصمت، ويظهر هذا الفقدان على شاشة العميل بعد أسابيع. لهذا السبب تُشحن ميزة الخط الاحتياطي وتقرير الغليفات المفقودة معاً: معالجة ما يمكن معالجته نصف المهمة فقط، والإبلاغ عمّا تعذّرت معالجته هو النصف الآخر
الاحتياط يحدث لكل عنقود، لا لكل نقطة رمز
الدقة التفصيلية هي التفصيل الذي يفصل بين تطبيق يعمل فعلاً وتطبيق يبدو معقولاً فقط. النص ليس سلسلة من الأحرف المستقلة. مقطع بلغة الديفاناغري، إيموجي بمعدّل لون بشرة، حرف أساسي مع علامات تركيب: كل واحد من هذه عنقود واحد يجب أن يرسمه خط واحد، لأن قرارات التشكيل بداخله تعتمد على جداول موجودة في ذلك الخط
تعالج PDFlibPas العناقيد ككل، لذا فإن العنقود الذي يغطيه خط احتياطي يُرسم بالكامل بواسطة ذلك الخط. تقسيم العنقود في منتصفه ورسم نصفه من الخط الأساسي ونصفه من خط احتياطي سينتج نتيجة موجودة تقنياً لكنها مكسورة بصرياً، وهو أمر يمكن القول إنه أسوأ من الفراغ الذي بدأت به. كما يُحافظ على ترتيب التشغيل، فالخط الاحتياطي داخل تشغيل من اليمين إلى اليسار لا يعيد ترتيب النص المحيط به؛ وتقوم الآلية نفسها على تخطيط الكتابة العمودية الموضح في الكتابة العمودية لليابانية والصينية
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutomaticFontFallback(1);
// ترتيب البحث: أول تطابق يفوز، لذا ضع الخطوط الأوسع تغطية في النهاية
Lib.AddFontFallback('Microsoft YaHei'); // الصينية المبسطة
Lib.AddFontFallback('Meiryo'); // اليابانية
Lib.AddFontFallback('Segoe UI Symbol');
Lib.AddFontFallback('Segoe UI Emoji');
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_REPORT);
Lib.AddTrueTypeFont('Arial', 1); // 1 = تضمين الخط
Lib.SetTextSize(11);
Lib.DrawText(72, 720, 'Invoice for 北京示例科技有限公司');
Lib.DrawText(72, 700, 'Delivery status: on time');
Lib.SaveToFile('invoice.pdf');
finally
Lib.Free;
end;
end;
رتّب السلسلة بعناية. تختار عملية المعالجة أول خط يغطي العنقود، فإذا وضعت خطاً واسعاً شاملاً لليونيكود في البداية فسيفوز بمعظم الحالات تقريباً، ولن يُستشار أبداً الخطوط المخصصة لكل نص كتابة التي اخترتها بعناية. ضع الخطوط المتخصصة أولاً والخط الشامل في النهاية
الإبلاغ أم الإيقاف: أي فشل تريده؟
تقبل SetMissingGlyphPolicy القيمة PDF_MISSING_GLYPH_REPORT، وهي الإعداد الافتراضي المتوافق، أو PDF_MISSING_GLYPH_ABORT. في ظل سياسة الإبلاغ تستمر عملية كتابة النص، وتُحذف نقاط الرموز غير القابلة للمعالجة كما كان الحال سابقاً، ويُسجَّل كل واحد منها. أما في ظل سياسة الإيقاف فتُرفض عملية كتابة النص قبل كتابة أي محتوى ويُضبط LastErrorCode على 521
اختر بناءً على الغرض من المستند. دفعة من التقارير الداخلية يجب أن تواصل العرض وتسجّل الثغرات، لأن تقريراً ناقصاً قليلاً اليوم أفضل من عدم وجود تقرير على الإطلاق. أما عقد مُلزم قانونياً، أو فاتورة، أو أي مستند يحمل اسماً، فيجب أن يتوقف، لأن حرفاً محذوفاً بصمت في اسم أحد الأطراف هو عيب تريد اكتشافه في عمليتك الخاصة وليس في نزاع قانوني. تفشل سياسة الإيقاف قبل الكتابة، بحيث لا يُترك خلفها أي تدفق محتوى نصف مكتمل
var
Lib: TPDFlib;
Report: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.SetMissingGlyphPolicy(PDF_MISSING_GLYPH_ABORT);
// ... إنشاء المستند ...
if Lib.DrawText(72, 660, CustomerName) <> 1 then
if Lib.LastErrorCode = PDFLIB_ERROR_MISSING_GLYPH then
begin
Report := Lib.GetMissingGlyphReportJSON;
// {"valid":false,"policy":1,"eventCount":1,"events":[
// {"sequence":1,"documentIndex":0,"page":1,"utf16Index":12,
// "codePoint":21271,"unicode":"U+5317","fontName":"Arial",
// "fontType":"TrueType","operation":"DrawText"}]}
EscalateToOperator(Report);
end;
finally
Lib.Free;
end;
end;
التقرير مصمَّم عمداً ليكون قابلاً للقراءة الآلية ومحدوداً. يحمل كل حدث رقم الصفحة، وفهرس UTF-16 داخل السلسلة، ونقطة الرمز بصيغتها الرقمية وبصيغة U+XXXX، والخط الذي تم اختياره، ونوعه، والعملية التي واجهت المشكلة، بحيث تستطيع تذكرة الدعم الفني تسمية الحرف بدقة بدلاً من وصف عرَض. يحتفظ المتتبع بأحدث 256 حدثاً، وهو عدد كافٍ لتشخيص مستند وصغير بما يكفي كي لا يحوّل تشغيل مَرَضي عملية التشخيص إلى مشكلة ذاكرة
يجب أن يتفق القياس والرسم
يستخدم قياس العرض قرارات الاحتياط نفسها المراعية للعناقيد المستخدمة في الرسم. قد يبدو هذا بديهياً، لكنه بالضبط ما تخطئ فيه معظم طبقات الاحتياط المطوَّرة داخلياً: فهي تُصلح مسار الرسم وتترك القياس على الخط الأساسي، فينتهي الأمر بكل مربع نص ومحاذاة يمينية وعمود جدول محسوباً من عروض لا تطابق ما تم رسمه فعلياً
بما أن كلا المسارين يتشاركان عملية المعالجة نفسها، فإن السلسلة التي تُقاس قبل رسمها تشغل العرض الذي قِيست عليه، بما في ذلك تشغيلات الاحتياط. وهذا ما يجعل تفعيل الاحتياط آمناً بشكل شامل بدلاً من قصره على الأماكن التي دققتها يدوياً فقط
لا يُضمَّن سوى ما استخدمته فعلياً
تُضمَّن الخطوط الاحتياطية بأسلوب كسول: فالخط الموجود في السلسلة الذي لم يعالج أي عنقود لا يساهم بشيء في الإخراج. المستند الذي يحتوي على حرف صيني واحد و5000 حرف لاتيني لا يحمل خط CJK كاملاً؛ بل يحمل ما أنتجته عملية التجزئة الفرعية لذلك الغليف الواحد فقط، وهو السلوك الموضح في تحسين حجم ملف PDF وتجزئة الخطوط الفرعية
هذا الأسلوب الكسول يجعل تكوين سلسلة واسعة أمراً رخيص التكلفة. سجّل الخطوط التي قد تحتاجها مجموعة مستنداتك عبر كل لغة تخدمها، وسيدفع كل ملف PDF على حدة ثمن ما استخدمه فعلياً فقط. أما بالنسبة للمستندات التي لم تُنشئها أنت، حيث تكون الخطوط المفقودة موجودة بالفعل داخل ملف قائم، فمسار الإصلاح مختلف وهو موضح في تضمين الخطوط المفقودة في ملف PDF قائم
يستحق أحد تحذيرات النشر أن يُذكر بوضوح: يعتمد الاحتياط على الخطوط المثبتة على الجهاز الذي يشغّل الكود. الخادم الذي لا تُثبَّت عليه خطوط CJK ليس لديه ما يحتاط به، وسيخبرك التقرير بذلك من أول مستند بدلاً من انتظار أول شكوى. اشحن الخطوط التي تعتمد عليها، وتأكد من ترخيص تضمينها
PDFlibPas هي مكتبة PDF لـ Delphi و C++Builder و Lazarus، مزودة بواجهات DLL وActiveX مطابقة، بحيث تتوفر واجهات برمجة الاحتياط والغليفات المفقودة أيضاً للمستدعين من خارج Pascal. التوثيق الكامل متاح على صفحة مكتبة PDFlibPas لـ Delphi