يتحقق PDFlibPas من ملفات PDF/UA-2 (ISO 14289-2) في Delphi عبر TPDFlib.CheckFilePDFUACompliance مع جزء هدف (target part) يساوي 2، وهو يشغّل ملف تعريف PDF/UA-2 منفصلاً: يحل أدوار البنية (structure roles) عبر نطاقات أسماء PDF 2.0 و /RoleMapNS، ويعامل الإدخال المبتور أو غير القابل للقراءة بوصفه فشلاً، ويعيد تقرير تغطية لا يدّعي الاعتماد الكامل أبداً
هذه الجزئية الأخيرة أهم مما تبدو. التحقق من إمكانية الوصول (accessibility validation) مجال تُنسخ فيه علامة الصح الخضراء إلى جدول بيانات المشتريات ولا يُساءل عنها مرة أخرى. أداة تحقق تتخطى بصمت نصف المعيار ولا تزال تطبع "pass" أسوأ من عدم وجود أداة تحقق أصلاً، لأنها تحوّل المجهول إلى معلوم زائف. يتخذ PDFlibPas الموقف المعاكس: افحص ما يمكن فحصه آلياً، وقل بصوت عالٍ ما لم يُفحص، وعامل كل "لم نستطع الإنهاء" بوصفه نتيجة (finding) لا نجاحاً
لماذا لا يمكنك التحقق من PDF/UA-2 بقواعد PDF/UA-1؟
PDF/UA-2 ليس PDF/UA-1 مع رقم جزء مرفوع فحسب. يستند PDF/UA-1 إلى PDF 1.7 وخريطة أدوار (role map) مسطحة واحدة؛ بينما يستند PDF/UA-2 إلى PDF 2.0 (ISO 32000-2)، حيث يمكن لكل عنصر بنية (structure element) أن ينتمي إلى نطاق اسم (namespace)، وكل نطاق يحمل خريطة أدواره الخاصة. أداة فحص UA-1 وجّهتها إلى ملف UA-2 ستفتقد المشاكل الحقيقية وتخترع مشاكل وهمية في آن واحد
المشاكل الوهمية هي الجزء المحرج. تفرض أدوات تحقق PDF/UA-1 عادةً قواعد حول عناصر Note التي تحمل معرفاً، وحول كيفية استخدام عنوان H غير المرقّم. لا تنتقل أي من القاعدتين. في نطاق بنية PDF 2.0، تُعبَّر الهوامش (footnotes) بـ FENote، والعناوين هي H1 حتى Hn بلا سقف للستة مستويات. وتحظر PDF/UA-2 دور H غير المرقّم حظراً صريحاً. طبّق منطق عناوين UA-1 على تلك الشجرة وستشتكي إما من أشياء يسمح بها المعيار أو تموّر على الشيء الوحيد الذي يحظره
لذلك فإن ملف تعريف PDF/UA-2 في PDFlibPas مسار كود منفصل، لا مجرد علم (flag) على المسار القديم. وهو يرفض أيضاً إعادة استخدام أرقام بنود UA-1. التشخيصات المشتركة مع مدقق UA-1 يُعاد ربطها بـ ISO 14289-2 في تقارير UA-2، وحيثما لم يُتحقق رقم البند مقابل نص المعيار، يترك التقرير حقل البند فارغاً بدلاً من التخمين. البند الفارغ أقل إرضاءً من رقم بند واثق، لكنه المخرج الوحيد القابل للدفاع عنه حين لا يؤكد أحدٌ المطابقة
كيف يحل PDFlibPas الأدوار عبر RoleMapNS
يحل PDFlibPas كل دور بنية إلى دور قياسي في نطاق اسم معروف، متتبعاً سلاسل /RoleMapNS عبر النطاقات، ويبلّغ عن أي سلسلة تنتهي على دور مخصص أو تدور في حلقة أو تخالف قواعد التعيين. هذا هو جوهر التحقق من بنية PDF/UA-2، لأن تقنيات المساعدة (assistive technology) لا تفهم سوى الأدوار القياسية؛ فالـ /InvoiceLine المخصص لا يعني شيئاً حتى يهبط على شيء مثل P أو TR
يبدأ المحلّل من اسم /S الخاص بالعنصر ونطاق /NS الخاص به. بلا نطاق اسم، يكون العنصر ضمنياً في نطاق PDF 1.7 القياسي وتنطبق /RoleMap القديمة في جذر شجرة البنية. ومع نطاق اسم، تنطبق /RoleMapNS الخاصة بقاموس النطاق نفسه، ويمكن أن يشير التعيين إلى دور في نطاق آخر عبر مصفوفة من عنصرين. الفحوصات النابعة من هذا ملموسة:
- يجب أن يكون النطاق المُشار إليه من عنصر بنية قاموساً غير مباشر (indirect dictionary) بـ URI صالح في
/NSومُدرجاً في مصفوفة/Namespacesلجذر شجرة البنية (التشخيص 10054) - يجب أن ينتهي المطاف بالدور المخصص إلى دور قياسي في نطاق PDF 1.7، أو نطاق PDF 2.0 (
http://iso.org/pdf2/ssn)، أو نطاق MathML؛ والسلاسل الدائرية والطرق المسدودة تُبلَّغ عنها بالرمز 10055 - لا يجوز إعادة تعيين دور قياسي إلى دور مخصص، ولا إعادة تعيين دور ضمن نطاقه الصريح نفسه
- يجب أن يحتوي جذر البنية على عنصر
Documentواحد بالضبط في نطاق PDF 2.0 (10057)، وأن يشير/Pلكل عنصر إلى أبيه الفعلي (10058) - عنوان
Hغير المرقّم، أوNoteقديمة من PDF 1.7 غير معيَّنة إلى دور من PDF 2.0، تُبلَّغ عنها بالرمز 10056
تفصيلة تستحق النسخ إلى أدواتك الخاصة: يتحقق PDFlibPas من كل تعيين مُعلن، لا من التعيينات التي تصلها الشجرة فحسب. الإدخال المعطوب في خريطة أدوار لا يستخدمه أي عنصر حالياً يظل عيباً كامناً (latent defect)، لأن تحديث المحتوى التالي سيبدأ استخدامه. ويُعامَل MathML بصدق أيضاً: نطاق MathML معترف به كهدف حلول صالح، لكن مفردات MathML ذاتها لا تُتحقق، وتقرير التغطية يقول ذلك صراحة
تشغيل فحص PDF/UA-2 من Delphi
TPDFlib.CheckFilePDFUACompliance هي نقطة الدخول ذات الاستدعاء الواحد: مرّر الملف وكلمة المرور وجزء الهدف والخيارات، وستحصل على مقبض قائمة نصوص (string list handle) يحوي المشاكل مرمَّزة. مقبض يساوي صفراً مع LastErrorCode يساوي صفراً يعني أن الفحوصات المنفذة لم تجد شيئاً؛ وLastErrorCode غير صفرية تعني أن الاستدعاء نفسه فشل
var
Lib: TPDFlib;
Issues, I: Integer;
begin
Lib := TPDFlib.Create;
try
// TargetPart: 0 = كشف من XMP pdfuaid:part، 1 = PDF/UA-1، 2 = PDF/UA-2
// Options: 1 = التوقف عند أول فشل، 0 = جمع كل مشكلة
Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
if Issues = 0 then
begin
if Lib.LastErrorCode <> 0 then
Writeln('Validation did not run, error ', Lib.LastErrorCode)
else
Writeln('No issues from the implemented PDF/UA-2 checks');
end
else
try
for I := 1 to Lib.GetStringListCount(Issues) do // العناصر تبدأ من 1
Writeln(Lib.GetStringListItem(Issues, I));
finally
Lib.ReleaseStringList(Issues);
end;
finally
Lib.Free;
end;
end;
اختر جزء الهدف بوعي. الهدف 0 يقرأ pdfuaid:part من بيانات XMP الوصفية ويختار الملف التعريفي منها، وهو الأنسب لأداة تحقق عامة للبريد الوارد. أما لخط إخراجك الخاص فمرّر 2 صراحة. إذا نسي المولّد لديك كتابة التعريف، يتراجع الكشف التلقائي إلى قواعد UA-1 وتتحقق من المعيار الخطأ؛ الـ 2 الصريح بدلاً من ذلك ينتج التشخيص 10050 للجزء المفقود، إضافة إلى 10051 إذا لم تكن pdfuaid:rev تساوي 2024 و10052 إذا لم تكن نسخة PDF الفعلية 2.0. وعلى جانب التأليف، تكتب TPDFlib.SetPDFUA2Mode ذلك التعريف وترفع النسخة إلى 2.0 وتختار نطاق بنية PDF 2.0 افتراضياً؛ نموذج النطاقات نفسه مشروح في استعراض نطاقات PDF 2.0 و PDF/A-4 و PDF/UA-2
ماذا يحدث عندما تنفد ميزانية أداة التحقق؟
يفشل PDFlibPas بالإغلاق (fail closed): حين يصطدم مدقق PDF/UA-2 بحد اجتياز أو حلقة أو حد محلل (parser limit) أو مستند لا يستطيع فتحه، يصدر تشخيصاً، والنتيجة بلا تشخيصات لا تُرقَّى أبداً إلى نجاح. التشخيص 10059 محجوز لحالة "القارئ لم يستطع الإنهاء" هذه تحديداً
الميزانيات صريحة ومنشورة في ملف تغطية JSON: عمق بنية 256، و100,000 كائن بنية، و256 قفزة على طول أي سلسلة خريطة أدوار. هذه الأرقام موجودة لأن أشجار البنية تأتي من إدخال غير موثوق. ملف معادٍ أو مجرد معطوب يمكنه تداخل العناصر آلاف المستويات، أو توجيه ابن إلى سلف، أو بناء خريطة أدوار تتسلسل عبر مئات النطاقات. أداة تحقق بلا ميزانيات إما تنهار أو تتعطل، وأداة تحقق بميزانيات لكن بلا تشخيصات تفعل شيئاً أخف وأسوأ: تتوقف مبكراً، لا تجد شيئاً خاطئاً في الجزء الذي قرأته، وتبلّغ عن النجاح
ينطبق المبدأ نفسه مستوىً أعلى. إذا أعاد القارئ الأساسي نتيجة أقل من 100 دون أسطر مشاكل، وهو ما يمكن أن يحدث عندما يتوقف التحليل قبل تشغيل أي قاعدة، يدرج PDFlibPas سطر 10059 حتى لا تُقرأ القائمة الفارغة خطأً على أنها نتيجة نظيفة. والاستثناء أثناء الفحص يصبح سطر 10059 أيضاً. هذا مقدار قليل من الكود، وهو يسدّ أكثر الطرق شيوعاً التي تكذب بها خطوط التحقق على أصحابها
مشاركة تحليل واحد مع PDF/A وبناء تقرير
للعمل الدفعي، تحلل واجهة برمجة خط التحقق (validation pipeline API) الملف مرة واحدة وتشغّل عدة فحوصات على التحليل المشترك. RunPDFUAValidationPipelineTask هو عضو PDF/UA في تلك العائلة وياخذ قيم جزء الهدف نفسها كالدالة ذات الاستدعاء الواحد، وهو ما يمنع فحصاً مسبقاً يجمع PDF/A مع PDF/UA-2 من دفع ثمن قراءتين كاملتين لمستند كبير
var
Pipe, PDFAIssues, UAIssues: Integer;
Json: WideString;
begin
Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
if Pipe = 0 then
Exit;
try
PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0); // 1 = PDF/A
UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0); // PDF/UA-2
// ... افحص وحرر المقبضين كما في المثال السابق ...
Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
// ComplianceTests: 1 = PDF/A، 2 = PDF/UA، 0 = كلاهما؛ ReportFormat 1 = JSON
Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
finally
Lib.ReleaseValidationPipeline(Pipe);
end;
end;
توسم صيغة التقرير المهيكلة قسم إمكانية الوصول بـ PDF/UA-2 و ISO 14289-2 عندما يعرف الملف نفسه كجزء 2، وكل قسم PDF/UA يحمل "fullCertification": false مع كائن التغطية المضمّن. ويضيف تقرير الفحص المسبق النصي سطراً يفيد بأن الفحوصات المنفذة فقط هي التي شُغّلت، وأن اعتماد إمكانية الوصول الكامل يحتاج تحققاً إضافياً ومراجعة بشرية. صيغ التقارير الأوسع ومعرّفات القواعد وأنماط بوابات CI مشروحة في دليل الفحص المسبق لـ PDF/A و PDF/UA
ما الذي يضمنه تقرير التغطية فعلاً؟
TPDFlib.GetPDFUAReadValidationCoverageJSON يضمن شيئاً واحداً: قائمة دقيقة بما فحصه مدقق PDF/UA-2 وما لم يفحصه. للجزء 2 يعيد fullCertification مثبتة على false، ومصفوفة checked، ومصفوفة unverified، وحدود الاجتياز. وهو لا يضمن أن الملف يمكن الوصول إليه، ومصمم بحيث لا يستطيع أي مسار كود أن يجعله يقول غير ذلك
var
Coverage: WideString;
begin
Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
// خزّنه بجانب نتيجة التحقق، لا بديلاً عنها
TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
if Pos('"fullCertification":false', Coverage) > 0 then
Writeln('Machine checks only; schedule a human accessibility review');
end;
قائمة checked تغطي التعريف (الجزء والمراجعة والبادئة)، ونسخة PDF الفعلية، وبيانات العنوان الوصفية، و/MarkInfo، وDisplayDocTitle، ومراجع النطاقات، وحل خرائط الأدوار، وجذر المستند، وأبوية البنية (structure parents)، وحلقات الشجرة، وقواعد H و Note. وقائمة unverified بالدقة نفسها: وسم المحتوى وترتيب القراءة، واحتواء البنية، والخطوط وترميز Unicode، والتعليقات التوضيحية والنماذج، واللغة الطبيعية، والأوصاف البديلة، ومفردات MathML، والمراجعة البشرية لإمكانية الوصول. عدة بنود منها ليست قابلة للحسم آلياً أصلاً. لا يستطيع أي محلل أن يخبرك هل يصف النص البديل الرسم فعلاً، أو هل يبدو ترتيب القراءة منطقياً لمن يستمع إليه
عامل تغطية JSON بوصفها جزءاً من النتيجة. أرشفها مع كل عملية تحقق، وحين يسألك مدقق حسابات ماذا كانت تعني كلمة "نجحت" في تاريخ معين، تسلّمه النطاق الدقيق بدلاً من إعادة تركيبه من الذاكرة ورقم إصدار
التحقق المتقاطع مقابل veraPDF
تم التحقق المتقاطع من نواة PDF/UA-2 مقابل أداة التحقق سطر الأوامر veraPDF 1.30.3 باستخدام ملفها التعريفي ua2، والنتائج تتطابق حيث تتداخل التغطية. عينة نواة صالحة مولَّدة باستقلالية نجحت في الأداتين معاً؛ وعينة تُعلن pdfuaid:rev بقيمة 2023 وعينة تستخدم دور H غير المرقّم فشلتا في الأداتين معاً
العينة الرابعة هي المستحقة للحديث. كانت تحوي محتوى رسومات غير موسوم إطلاقاً، وعلّمت عليها veraPDF بينما لم تفعل ذلك فحوصات نواة PDFlibPas، لأن وسم المحتوى خارج التغطية الحالية. تلك الفجوة هي بالضبط سبب تصدُّر content-tagging-and-reading-order قائمة unverified. تشغيل أداة تحقق ثانية تأمين رخيص، والاختلاف بين أداتين غالباً أسرع طريق لإيجاد حدّ ما تفحصه كل واحدة. أما جانب التأليف للمشكلة نفسها، أي بناء أشجار تنجح من البداية أصلاً، فراجع دليل شجرة بنية PDF الموسومة
الأسئلة الشائعة
هل يمنح PDFlibPas الملف اعتماد مطابقة PDF/UA-2؟
لا. يشغّل PDFlibPas فحوصات الآلة المنفذة لـ PDF/UA-2 ويعيد تقرير تغطية يكون فيها fullCertification دائماً false، مع سرد كل مجال ما يزال بحاجة إلى أدوات أخرى أو مراجعة بشرية
ما قيمة TargetPart التي أمررها إلى CheckFilePDFUACompliance؟
مرّر 2 عندما تعلم أن الملف يجب أن يكون PDF/UA-2، حتى يُبلَّغ عن التعريف المفقود. مرّر 0 للكشف التلقائي عن الجزء من XMP عبر pdfuaid:part، و1 لفرض قواعد PDF/UA-1
ماذا يعني التشخيص 10059 في تقرير PDF/UA-2؟
التشخيص 10059 يعني أن أداة التحقق لم تتمكن من الإكمال: تعذّر تحليل الملف أو فتحه، أو تجاوز حد اجتياز، أو عُثر على حلقة، أو وقع استثناء. يُحسب دائماً فشلاً، ولا يُحسب نجاحاً أبداً
يأتي التحقق من PDF/UA-2 وخط التحقق المشترك وواجهات تأليف PDF 2.0 ضمن مكتبة losLab PDF لـ Delphi، إلى جانب الفحص المسبق لـ PDF/A وتقارير التحقق المهيكلة