يتيح PDFium Component لتطبيق Delphi أن يقرر أي بايتات خط تُستخدَم حين يُشير PDF إلى خط لا يُضمّنه. يُثبِّت ConfigureSystemFontProvider تنفيذًا لـ IPdfSystemFontProvider يستقبل كل طلب تخطيط خط يُصدره PDFium، كاملًا باسم الوجه، والوزن، وعلامة المائل، ومجموعة الأحرف، وعائلة التباعد، ويُجيب ببايتات TrueType أو TrueType Collection أو OpenType الواجب استخدامها
وهذا موجود لأن الخطوط غير المضمَّنة يانصيب رسم. فملف PDF يُسمّي Arial ولا يُضمّن شيئًا يُرسَم بخط Arial على محطة عمل، وببديل متوافق القياسات على خادم Linux، وبأي شيء يجده مخطِّط النظام المضيف على صورة حاوية مقفلة. وتبدو الفاتورة نفسها مختلفة في كل منها، وتتحرك فواصل الأسطر، ويستلم العميل مستندًا لا يطابق النسخة المؤرشفة
لماذا لا تُثبَّت الخطوط على الخادم فحسب؟
أحيانًا يكون هذا هو الحل، وحين يكون كذلك، خذ به. لكنه يفشل في ثلاث حالات شائعة. فقد يحظر الترخيص تثبيت خط على خادم للرسم الآلي. وتُعاد بناء صور الحاويات بشكل متكرر، ويختفي خط ثُبِّت يدويًا مع النشر التالي. وتحتاج سير العمل المُنظَّمة إلى أن يكون كِدس الرسم قابلًا لإعادة الإنتاج من مصنوعات تحت التحكم بالإصدارات، وهو ما ليس عليه تثبيت خط على مستوى الجهاز
ويُعالج المزوّد الحالات الثلاث بنقل القرار إلى تطبيقك. فتُشحَن الخطوط كموارد تتحكم بها أنت، وسياسة التخطيط كود يمكنك مراجعته، ويرسم الملف الثنائي نفسه بشكل متطابق في كل مكان لأن لا شيء يعتمد على ما يصادف وجوده مثبَّتًا
تثبيت مزوّد
يجب أن يحدث الضبط قبل تحميل المكتبة. يقبل PDFium بنية معلومات خط النظام عند التهيئة ويحتفظ بالمقابض التي يُصدرها بعد ذلك، بحيث إن تبديل مزوّد أثناء فتح مستندات كان سيُبطل مقابض خط لا يزال PDFium يحتفظ بها؛ ويرفض المكوّن ذلك صراحة بدلًا من السماح له بإفساد رسم:
uses
PDFium;
type
TAppFontProvider = class(TInterfacedObject, IPdfSystemFontProvider)
public
function ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
end;
function TAppFontProvider.ResolveFont(const Request: TPdfSystemFontRequest;
out Font: TPdfSystemFontData): Boolean;
var
Path: string;
begin
// تخطيط حتمي: اسم الوجه إضافةً إلى الوزن والمائل يقررون
// أي ملف نشحنه لهذا الطلب
Path := MapFaceToBundledFile(Request.FaceName, Request.Weight,
Request.Italic, Request.Charset);
Result := Path <> '';
if not Result then
Exit;
Font.FaceName := Request.FaceName;
Font.FontData := LoadFileBytes(Path); // بايتات sfnt أو TTC كاملة
Font.Charset := Request.Charset;
Font.TTCIndex := 0; // الفهرس داخل مجموعة
end;
var
Policy: TPdfSystemFontPolicy;
begin
Policy := TPdfSystemFontPolicy.Default;
Policy.AllowDefaultFallback := False; // المضيف يقرر كل شيء
Policy.AllowFaceSubstitution := False; // رفض اسم وجه مختلف
Policy.MaxFontBytes := 32 * 1024 * 1024;
Policy.MaxCacheEntries := 64;
ConfigureSystemFontProvider(TAppFontProvider.Create, Policy);
// حمّل المكتبة وافتح المستندات الآن فقط
end;
ويعمل التفكيك بالترتيب المعاكس: يُفصَل المزوّد عن PDFium أولًا، ثم تُلغى تحميل المكتبة. وتخطي الفصل يترك مقابض خط أصلية تشير إلى كائنات Pascal على وشك التحرير، وهو انتهاك الوصول الكلاسيكي عند الإغلاق في كود يمزج واجهات معدودة المراجع مع مكتبة C
ما الذي تقرره أعلام السياسة فعليًا
إن AllowDefaultFallback هو المفتاح بين وضعَي تشغيل. فإذا كان معطَّلًا، فإن طلبًا يرفضه المزوّد يفشل ببساطة، وهذا ما تريده أثناء إثبات أن كل خط في مجموعة ملفات مُحصى: تصبح أي ثغرة مرئية فورًا بدلًا من التستر عليها. وإذا كان مفعَّلًا، تُفوَّض الطلبات غير المحلولة إلى المخطِّط الذي تُعيده FPDF_GetDefaultSystemFontInfo، بينما يظل العالم الخارجي يرى غلاف مقبض موحّدًا واحدًا، مع توجيه اسم الوجه، ومجموعة الأحرف، وبيانات الجدول، وحذف الخط بشكل صحيح وفق المصدر
ويحكم AllowFaceSubstitution ما إذا كان بإمكان المزوّد الإجابة باسم وجه مختلف عن المطلوب. وتعطيله يجعل الاستبدال قرارًا صريحًا لا حادثًا، وهو أمر مهم حين يُسمّي مستند خطًا تختلف قياساته بما يكفي لتغيير التقسيم إلى صفحات
ويتحقق المكوّن من صحة كل استجابة مزوّد قبل وصولها إلى PDFium: تُرفَض البيانات الفارغة، وتُرفَض الخطوط الضخمة بمقارنتها بـ MaxFontBytes، ويُتحقَّق من فهرس TTC، وتُقدَّم جداول sfnt الفردية من دليل الخط حين يطلب PDFium جدولًا لا الملف بأكمله. وهذه القدرة الأخيرة تعني أن بإمكان مزوّد تسليم ملف خط كامل وترك المكوّن يُجيب عن استعلامات على مستوى الجدول، بدلًا من كشف كائنات Pascal الخام عبر ABI الخاص بـ C
التخزين المؤقت من دون بيانات خط معلَّقة
تتكرر طلبات تخطيط الخط باستمرار أثناء الرسم، لذا تُخزَّن الاستجابات مؤقتًا بمفتاح يُغطي كل معامِل لاختيار الخط، ويُطرَد وفق ترتيب "الأقل استخدامًا مؤخرًا" محدود. والدقّة تكمن في عمر البيانات: قد يكون PDFium لا يزال يقرأ بايتات خط طُرد مُدخَل ذاكرته المؤقتة للتو
تُخزِّن ذاكرة التخزين المؤقت مصفوفات ديناميكية معدودة المراجع، ويحمل كل مقبض أصلي لقطته الخاصة، بحيث يُسقِط الطرد مرجعًا لا يُحرِّر ذاكرة قيد الاستخدام. ويُحرِّر استدعاء الحذف المقبض ويحافظ على عدد نشط. وعمليًا، يعني هذا أنه يمكن ضبط MaxCacheEntries من أجل الذاكرة من دون أي خطر لسحب البيانات من تحت رسم قيد التنفيذ
هل يُستدعى المزوّد على خيطي؟
لا، ليس بالضرورة. قد يستدعي PDFium المخطِّط من خيوط عامل خاصة به، لذا يجب أن يكون التنفيذ آمنًا للخيوط. وتُحمى العدادات المشتركة، وذاكرة التخزين المؤقت، ومراقبة الضبط، كل منها داخل المكوّن بقسم حرج خاص بها، لكن الكود داخل ResolveFont عليك أنت جعله آمنًا
وأكثر الأشكال أمانًا مزوّد لا يلمس أي حالة مشتركة قابلة للتغيير: اقرأ من جدول مبني عند بدء التشغيل، وحمّل بايتات من ملف أو مورد، وأعِد النتيجة. وإذا احتاج بحث إلى ذاكرة مشتركة خاصة بك، فاحمِها. وأبقِ الاستثناءات داخل تنفيذك، لأن استثناء Pascal يجب ألا يفكّ لفّه عبر كِدس PDFium أبدًا؛ يلتقط المكوّن عند حدود ABI الخاص بـ C ويحوّله إلى فشل أو رجوع افتراضي اختياري، لكن الاعتماد على ذلك كتدفق تحكم عادي يكلّف أداءً ويُخفي أخطاءً. وتتبع قواعد تعدد الخيوط لبقية المكوّن المبادئ نفسها الموجودة في انضباط قفل الرسم
إثبات التخطيط في بيئة الإنتاج
تُحوِّل الإحصائيات استبدال الخط من تخمين إلى شيء يمكنك التأكيد عليه. تُبلغ GetSystemFontProviderStatistics عمّا إذا كان مزوّد مضبوطًا ومُثبَّتًا، وكم طلب تخطيط جرى، وكيف تمت تلبيتها، مقسَّمة إلى إصابات ذاكرة مؤقتة، وإصابات مزوّد، وإصابات رجوع افتراضي، إلى جانب الاستجابات المرفوضة، والطلبات الفاشلة، والمقابض الحية، والخطوط المخزَّنة مؤقتًا:
var
Stats: TPdfSystemFontStatistics;
begin
Stats := GetSystemFontProviderStatistics;
Writeln(Format('requests=%d cache=%d provider=%d fallback=%d',
[Stats.MapRequests, Stats.CacheHits, Stats.ProviderHits,
Stats.DefaultFallbackHits]));
Writeln(Format('rejected=%d failed=%d handles=%d cached=%d',
[Stats.RejectedProviderResponses, Stats.FailedRequests,
Stats.ActiveHandles, Stats.CachedFonts]));
// في تشغيل مطابقة مع تعطيل الرجوع الاحتياطي، أي إصابة رجوع
// أو طلب فاشل يعني أن مستندًا أشار إلى خط لا نشحنه
if (Stats.DefaultFallbackHits > 0) or (Stats.FailedRequests > 0) then
raise Exception.Create('unmapped font encountered - update the font set');
end;
وارتفاع عدد RejectedProviderResponses إشارة إلى أن مزوّدًا يُجيب ببيانات ترفضها السياسة، عادة ملف ضخم أو وجه مُستبدَل، ويستحق التنبيه لأن تلك الطلبات تتدهور بصمت إلى رجوع احتياطي أو فشل. ولتشخيص الخطوط التي يحتاجها مستند فعليًا قبل بناء جدول التخطيط، يُدرج مسار الفحص في تحليل خصائص خطوط PDF الخطوط المضمَّنة وغير المضمَّنة لكل مستند
وتشترك تهيئة الخطوط، والرسم، واستخراج النص في نسخة المكتبة نفسها عبر Delphi وC++Builder وLazarus؛ وتفاصيل النشر موصوفة على صفحة PDFium Component لـ Delphi