يمكن لمكتبة losLab PDF تضمين برامج الخطوط المفقودة لملف PDF محمل بالفعل باستدعاء واحد: تقوم EmbedMissingFonts بالمرور على كل قاموس خطوط في المستند، وتحديد موقع خط النظام المثبت المطابق بواسطة اسم BaseFont الخاص به، وتكتب برنامج الخط مرة أخرى في الملف. بالنسبة للفرق التي تقوم بإصلاح مستندات الطرف الثالث التي تفشل في التحقق من صحة PDF/A عند تضمين الخطوط، فإن هذا هو الإصلاح الذي يزيل خطأ ما قبل الرحلة (preflight) رقم 00030
هذا السيناريو شائع بشكل محبط. يستقبل خط تدفق استيعاب الأرشيف ملفات PDF من الموردين أو العملاء أو مكتب المسح الضوئي؛ وتظهر المستندات بشكل جيد على كل مكتب في المبنى; ثم يرفض مدقق صحة PDF/A الدفعة بأكملها مع تكرار نفس الشكوى مرة واحدة لكل ملف: خط واحد على الأقل غير مضمن. لن يقوم أحد في المراحل السابقة بإعادة إنشاء الملفات، لذا يتعين على خط التدفق إصلاحها. يغطي هذا المقال مسار الإصلاح هذا. وهو مكمل لـ مقال ما قبل الرحلة، والذي يغطي اكتشاف انتهاكات PDF/A وPDF/UA: يخبرك هذا الجزء بالمستندات المعطلة، بينما يقوم هذا المقال بإصلاح الطريقة الأكثر شيوعًا لتعطلها
لماذا يتطلب PDF/A تضمين كل خط؟
تتطلب مواصفة ISO 19005-1 §6.3.4 أن يحمل كل خط يستخدمه مستند متوافق برنامج الخط الخاص به داخل الملف، لأن الوعد الكامل لـ PDF/A هو قابلية إعادة الإنتاج: يجب أن يظهر المستند بشكل متطابق على جهاز بعد خمسين عامًا من الآن لا يشارك أي خطوط مع الجهاز الذي أنتجه. الخط غير المضمن هو تعليمات للذهاب والبحث عن Arial في مكان ما في نظام العرض، وموقف المعيار هو أن "مكان ما في نظام العرض" ليس ضمانًا أرشيفيًا. مهما كانت الصور الرمزية والمقاييس والتغطية التي يمتلكها الخط البديل، فهذا هو ما يحصل عليه القارئ، وقد لا يكون هو ما رآه المؤلف
المتسبب التاريخي هو اتفاقية Standard 14. وعدت PDF 1.0 بأن كل برنامج عرض يشحن Helvetica وTimes وCourier وSymbol وZapfDingbats، لذلك تعلمت المولدات الإشارة إلى تلك الخطوط بالاسم وعدم تضمين أي شيء، وثلاثون عامًا من الأدوات لا تزال تفعل ذلك بالضبط. تأخذ مكتبة losLab PDF هذا المتطلب على محمل الجد لدرجة أنه في وضع إنشاء PDF/A يكون AddStandardFont غير فعال عمدًا: لا تشحن المكتبة برامج خطوط Standard 14، ولا يمكنها تضمين ما لا تملكه، وترفض كتابة مرجع غير مضمن في مستند يدعي التوافق. حيث ترجع القيمة 0 دون تحديد خط، لذا يجب أن يستخدم مستند PDF/A الوظيفة AddTrueTypeFont مع التضمين بدلاً من ذلك، ويتم ترقية أي طلب لـ Embed=0 بصمت إلى Embed=1 أثناء نشاط وضع PDF/A. هذا هو جانب الكاتب. المشكلة الأصعب هي جانب القارئ: مستند كتبه شخص آخر بالفعل، مليء بقواميس الخطوط التي لم تنشئها
كيف تقوم EmbedMissingFonts بإصلاح مستند محمل؟
تقوم مكتبة losLab PDF بإصلاح الخطوط في مكانها بدلاً من إعادة بنائها. عندما يكتب مولد PDF خط TrueType غير مضمن، فإن قاموس FontDescriptor الذي ينتجه يكون مكتملًا بالفعل: FontName، FontBBox، Flags، Ascent، Descent، StemV، كلها موجودة. الشيء الوحيد الذي يفصله عن الخط المضمن هو غياب إدخال واحد، وهو مرجع تدفق /FontFile2 الذي يحتوي على برنامج الخط الفعلي. لذلك لا تلمس EmbedMissingFonts قاموس الخط، أو الترميز، أو مصفوفة العروض، أو أي تدفق محتوى يشير إلى الخط باسم المورد. إنها تقرأ برنامج الخط المطابق من النظام، وتضغطه في كائن تدفق جديد، وتلحق مرجع /FontFile2 واحدًا (أو /FontFile3 لخطوط CIDFontType0) إلى FontDescriptor الموجود بالفعل هناك. كل ما تشير إليه صفحات المستند يبقى في مكانه تمامًا، وهو ما يجعل العملية آمنة للتشغيل على ملفات لا تتحكم فيها
تشمل التغطية كلا بنيتي الخطوط التي ستواجهها في الممارسة العملية: خطوط TrueType البسيطة وخطوط Type0/CID المركبة، وهي النوع المنتج لنصوص CJK ومخرجات Unicode الحديثة. تعمد عملية المرور إلى تعداد كل قاموس Font في شجرة كائنات المستند بدلاً من الاعتماد على جولة الموارد صفحة بصفحة، وبالتالي يتم أيضًا التقاط الخطوط المشار إليها من التعليقات التوضيحية أو المشتركة عبر الصفحات. واجهة برمجة التطبيقات (API) هي استدعاء واحد للمستند المحمل
var
PDF: TPDFlib;
Repaired: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('supplier-invoice.pdf', '') <> 1 then
raise Exception.Create('Could not load PDF');
// Walks every Font dictionary; returns how many fonts
// gained a font program. Fonts whose program cannot be
// found on the system are skipped, not failed.
Repaired := PDF.EmbedMissingFonts;
Writeln(Format('%d font program(s) embedded', [Repaired]));
PDF.SaveToFile('supplier-invoice-repaired.pdf');
finally
PDF.Free;
end;
end;
هناك تفصيل واحد جدير بالمعرفة لأنه يشرح لماذا يعمل مطابقة الأسماء بشكل أفضل من مقارنة السلاسل النصية الساذجة: تقوم المكتبة بتطبيع أسماء BaseFont قبل البحث عنها. يتم تجريد البادئات الفرعية (نمط ABCDEF+ المكون من ستة أحرف كبيرة وعلامة زائد)، ويتم حل اللاحقات بنمط PostScript مثل ArialMT إلى Arial,ويتم اكتشاف ملفات TrueType Collection وفك حزمتها بحيث يظل شكل الخط الموجود داخل ملف .ttc مضمنًا بشكل صحيح
التحقق من الإصلاح باستخدام تقرير ما قبل الرحلة (preflight)
CreatePreflightReport هي خطوة التحقق، والعملية مغلقة عمدًا: نفس التدقيق الذي أدان الملف يجب أن يكون هو الذي يبرئه. رمز الخطأ 00030 هو نتيجة التدقيق العميق لـ PDF/A والتي تنص على أن "خطًا واحدًا على الأقل غير مضمن (FontFile/FontFile2/FontFile3 مفقود)"، ويتم الإبلاغ عنه ضد الملف ككل، وبالتالي فإن خطًا واحدًا تم إهماله يبقيه نشطًا. قم بتشغيل التقرير على الملف المصدر، ثم قم بالإصلاح والحفظ وتشغيله مرة أخرى على المخرجات
function HasFontEmbeddingViolation(PDF: TPDFlib;
const FileName: string): Boolean;
var
Report: string;
begin
// ComplianceTests = 1 selects the PDF/A checks
Report := PDF.CreatePreflightReport(FileName, '', 1, 0);
Result := Pos('00030', Report) > 0;
end;
للحصول على عرض لكل خط بدلاً من حكم لكل ملف، قم بتحميل المستند الذي تم إصلاحه مرة أخرى وقم بالتعداد: يبلغ FindFonts متبوعًا بـ SelectFont و GetFontIsEmbedded عن حالة التضمين خطًا بخط، وهي الأداة المناسبة عندما تحتاج وظيفة الدفعة (batch job) إلى تسجيل أي شكل خط بالضبط في أي ملف لم يكن بالإمكان إصلاحه. يظهر نفس نمط التعداد في المقال الخاص باستخراج النصوص والصور والخطوط من ملفات PDF المحملة، حيث يغذي الاستخراج بدلاً من الإصلاح
ماذا يحدث عندما لا يكون الخط مثبتًا على النظام؟
تتجاوز EmbedMissingFonts أي خط لا يمكنها العثور على برنامجه، وتبلغ عن التجاوز من خلال قيمتها المرجعة: إذا كانت النتيجة أقل من عدد الخطوط غير المضمنة التي قمت بعدّها، فإن الفرق هو الخطوط التي لا يمتلكها النظام. هذا هو وضع الفشل النزيه، وهو أفضل من البدائل، لأن ابتكار برنامج بديل لخط مسمى في المستند من شأنه أن يغير العرض، وهو بالضبط ما يجب ألا يفعله الإصلاح الأرشيفي أبدًا. في هذه الحالات، توفر مكتبة losLab PDF الوظيفة EmbedFontProgramFromFile، والتي تقوم بتضمين ملف .ttf أو .otf مقدم من المستدعي في الخط المسمى، بحيث يمكن لخط التدفق شحن الخطوط الخاصة بالشركة التي يتوقع مواجهتها والرجوع إليها عمدًا
var
I, FontID: Integer;
begin
PDF.FindFonts;
for I := 1 to PDF.FontCount do
begin
FontID := PDF.GetFontID(I);
if (FontID > 0) and (PDF.SelectFont(FontID) = 1) then
if PDF.GetFontIsEmbedded = 0 then
// Try the installed system font first, then fall back
// to a font file shipped alongside the application
if PDF.EmbedFontProgram(PDF.FontName) = 0 then
PDF.EmbedFontProgramFromFile(PDF.FontName,
'fonts\CorporateSans.ttf');
end;
end;
هناك حدّان يستحقان التصريح بهما بوضوح. أولاً، لا يتم إصلاح خطوط Type1 في التنفيذ الحالي: يتطلب إدخال /FontFile الخاص بها بنية PFB ثلاثية الأجزاء مع مفاتيح طول صريحة، وتتخطاها المكتبة بدلاً من كتابة تدفق مشوه؛ وهي نادرة في المستندات الحديثة ولكنها تظهر في الأرشيفات القديمة. ثانياً، تضمين الخط هو عمل ترخيص. تنتمي أذونات تضمين خط TrueType إلى مسبك الخطوط الخاص به، ويجب على خط تدفق الإصلاح الذي يضع برامج الخطوط المرخصة في المستندات التي تغادر المؤسسة أن يطلب من شخص ما تأكيد أن تراخيص الخطوط تسمح بذلك بالفعل. ستقوم المكتبة بما تطلبه؛ أما ما إذا كان يحق لك الطلب فهو سؤال موجه إلى القسم القانوني لديك، وليس إلى المترجم (compiler) الخاص بك
التضمين ضروري، لكنه ليس كافيًا
يؤدي إصلاح الخطوط إلى إزالة الخطأ 00030، وليس أي شيء آخر. المستند الذي يفشل في PDF/A بسبب التشفير، أو فقدان بيانات XMP الوصفية، أو مساحة ألوان تعتمد على الجهاز بدون OutputIntent، أو غياب خرائط ToUnicode سيظل يفشل حتى بعد تضمين كل خط، ولهذا السبب ينتمي الإصلاح إلى حلقة مدفوعة بتقرير ما قبل الرحلة بدلاً من استبدالها. قم بتشغيل التقرير الكامل، وأصلح ما يسميه، ودع التقرير يخبرك عندما تنتهي. هناك أيضًا بُعد للتكلفة: يصل حجم برنامج خط CJK الكامل إلى ميغابايت، لذا فإن تضمين العديد منها يمكن أن يضخم مستندًا صغيرًا بشكل كبير. الوزن المقابل هو التجزئة (subsetting)، والتي تمت تغطيتها في المقال الخاص بتجزئة الخطوط وتحسين حجم ملف PDF، والذي يقلص كل برنامج مضمن إلى الصور الرمزية التي يعرضها المستند بالفعل
منع المستندات الجديدة من التراجع
تعد SetEmbedAllFonts هي النصف الوقائي من نفس الميزة: حارس من جانب الكاتب يمنع الكود الخاص بك من إنتاج المستندات التي يقوم هذا المقال بإصلاحها. مع تفعيل SetEmbedAllFonts(1)، يتم ترقية أي استدعاء لاحق لـ AddTrueTypeFont يطلب Embed=0 إلى مرجع مضمن، مما يمد الضمان الذي يفرضه وضع PDF/A بالفعل إلى كل مستند. وهو يؤثر على الخطوط المضافة بعد الاستدعاء، وليس الخطوط الموجودة بالفعل في ملف محمل، لذا فإن تقسيم العمل واضح: SetEmbedAllFonts للمستندات التي تنشئها، و EmbedMissingFonts للمستندات التي ترثها
PDF.NewDocument;
PDF.SetEmbedAllFonts(1);
// From here on, AddTrueTypeFont(Name, 0) behaves
// like AddTrueTypeFont(Name, 1): no non-embedded
// reference can reach the output file
كلا النصفين، الحارس من جانب الكاتب ومسار التحميل والإصلاح والحفظ، هما جزء من مكتبة losLab PDF لدلفي، وC# وVB.NET، جنبًا إلى جنب مع محرك ما قبل الرحلة الذي يتحقق من النتيجة؛ وتحتوي صفحة المنتج على مرجع واجهة برمجة تطبيقات الخطوط الكامل بما في ذلك استدعاءات التضمين والتجزئة لكل خط