يحتوي قاموس PDF Catalog على مفتاح تنقّل واحد مطلوب فقط: /Pages. يجب أن يشير هذا المفتاح إلى كائن غير مباشر من النوع /Pages، والذي يحمل بدوره المصفوفة /Kids والعدد الكلي للصفحات /Count. إذا أزلت هذا المؤشر فلن يتمكن أي قارئ مطابق للمواصفة من العثور على صفحة واحدة في الملف. تنص ISO 32000-1 §7.7.2 بوضوح على هذه النقطة: يجب أن يحتوي Catalog على إدخال /Pages، ويجب أن يكون للكائن المشار إليه النوع /Pages. الملفات التي تخالف هذا الشرط ليست مجرد غير مطابقة، بل تكون مكسورة بنيويًا بطريقة يتعامل معها معظم المحللات على نحو سيئ.
ما الذي تنص عليه المواصفة فعلاً
يحتوي PDF مطابق للمواصفة في الحد الأدنى على ثلاثة كائنات على الأقل. الكائن 1 هو Catalog، والكائن 2 هو جذر Pages، وما بعد الكائن 3 هو قواميس Page منفردة. يشير Catalog إلى جذر Pages، ويسرد جذر Pages أبناءه داخل /Kids، وتحمل كل Page مرجعًا عكسيًا /Parent. السلسلة كلها ثنائية الاتجاه بحكم التصميم، بحيث يمكن للمحلل أن يبدأ من أي طرف وأن ينتقل إلى أي صفحة بزمن O(log n) في الأشجار المتوازنة.
% Minimal conforming structure (ISO 32000-1 §7.7.2)
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 5 0 R /Resources << >> >>
endobj
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj
يمكن أن تكون شجرة Pages متداخلة. فالمستند الذي يضم آلاف الصفحات يجمع الصفحات عادةً في عقد وسيطة تحمل أيضًا النوع /Pages، ولكل منها /Kids الخاصة بها و/Count يعكس الشجرة الفرعية أسفلها. يساوي /Count في العقدة الجذرية دائمًا العدد الكلي للصفحات. وهذا هو العدد الذي تعرضه برامج العرض في حقل رقم الصفحة قبل أن تحلل صفحة واحدة، لأن قراءة عدد صحيح واحد من الكائن 2 أرخص بكثير من اجتياز الشجرة كلها.
كيف يبدو ملف بلا Pages
عادةً ما تأتي الملفات التي تفتقد قاموس Pages من مولدات PDF تكتب كائنات الصفحات مباشرة من دون تجميعها في شجرة، أو من تلف يزيل العقدة الجذرية مع إبقاء كائنات Page الطرفية سليمة. يكون Catalog في مثل هذا الملف إما خاليًا تمامًا من المفتاح /Pages، أو يحتفظ بمرجع إلى كائن لم يعد موجودًا في جدول المراجع المتقاطعة.
% Non-conforming: Catalog with no /Pages reference
1 0 obj
<< /Type /Catalog >>
endobj
% Page objects exist but are unreachable from the Catalog
5 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj
15 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 16 0 R /Resources << >> >>
endobj
25 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 26 0 R /Resources << >> >>
endobj
المحلل الذي يلتزم بالمواصفة سيقرأ Catalog، ويحاول حل /Pages، فلا يجد شيئًا أو يجد مرجعًا ميتًا، ثم إما يرفع خطأ أو يعرض صفر صفحات. ما لا يجوز له فعله هو أن يواصل العمل كما لو كان الملف يضم صفر صفحات وينجح بصمت؛ فذلك ينتج مخرجات فارغة تبدو صحيحة لأدوات التشغيل الآلي وخاطئة لكل إنسان يفتحها.
لماذا تتعطل برامج التحليل
تخصص معظم محللات PDF جدول الصفحات الداخلي عند التحميل استنادًا إلى قيمة /Count في جذر Pages. عندما يغيب ذلك الجذر، قد يقرأ المحلل صفرًا، فيخصص لا شيء، ثم يفك مرجعًا فارغًا لأول مرة يطلب فيها أي كود الصفحة 1، أو قد يقرأ بيانات غير صالحة ويخصص مخزنًا خاطئًا على نحو شديد. ولا يكون أي من المخرجين مريحًا. إن انتهاك الوصول عند 0x008E5D78 الذي يظهر في سجلات التعطل أثناء معالجة مثل هذا الملف هو بالضبط هذا: فك مرجع لمؤشر فارغ داخل مسار الوصول إلى الصفحات، بسبب غياب البنية التي افترض المحلل أنها ستكون موجودة دائمًا.
الافتراض التصميمي الأساسي معقول. فالغالبية العظمى من ملفات PDF الموجودة فيها قاموس Pages. والمحللات التي تتجاوز فحص الوجود لتوفير بضع تعليمات ليست متهورة، بل تحسن للحالة الشائعة. أما الملفات التي تعاقب هذا التحسين فهي نادرة بما يكفي لأن لا تصادفها البرمجيات الإنتاجية أبدًا إلى أن يحدث ذلك، وعندها يكون التعطل قابلاً للتكرار ومحيّرًا إذا لم يقرأ المهندس §7.7.2.
الاسترداد من دون شجرة Pages
إذا كان على المحلل التعامل مع هذه الملفات بدلًا من رفضها، فإن الاسترداد يتبع مسارًا متوقعًا: فحص كل كائن غير مباشر في جدول المراجع المتقاطعة، وجمع الكائنات التي تحمل /Type /Page، ثم فرزها حسب رقم الكائن. لا يضمن ترتيب أرقام الكائنات أن يطابق ترتيب القراءة في المواصفة، لكن في الممارسة تميل المولدات التي تحذف شجرة Pages إلى إصدار الصفحات متسلسلة، لذلك يكون ترتيب أرقام الكائنات صحيحًا في أغلب الأحيان.
الفحص نفسه رخيص. قبل تتبع مؤشر /Pages في Catalog، تأكد من أن المؤشر موجود، وأنه يشير إلى كائن حقيقي، وأن /Type للكائن المحلول يساوي /Pages. إذا فشلت أي من هذه الشروط الثلاثة، فانتقل إلى الفحص الخطي. الفحص أبطأ من اجتياز الشجرة في المستندات الكبيرة، لأنه يقرأ ترويسة كل كائن بدلًا من اتباع مسار متوازن، لكنه يعمل، ولملف مكسور أصلًا تكون الدقة أهم من السرعة.
أحد الحالات الحدية التي لا يحلها الفحص الخطي تلقائيًا هو ترتيب الصفحات. من دون مصفوفة /Kids التي تحدد التسلسل، يكون الترتيب "الصحيح" غير معرّف في المواصفة. ترتيب أرقام الكائنات هو الخيار العملي الافتراضي؛ وإذا كان الملف مهمًا بما يكفي لمعالجته بعناية، ففحص ما إذا كانت كائنات Page تحمل /StructParents صريحة أو مراجع تعليقات توحي بتسلسل قراءة يستحق الجهد الإضافي.
الآثار على مولدات PDF
بالنسبة إلى من يكتب مولد PDF بدلًا من محلل، فالعبرة محددة: أخرج دائمًا جذر Pages قبل إغلاق الملف. فوجود Catalog من دون إدخال /Pages لا ينتج PDF صالحًا تحت أي مراجعة من المواصفة. المولدات التي تبني كائنات الصفحة أثناء التنفيذ وتجمع الشجرة عند الإنهاء، وهو النهج الذي تستخدمه معظم كتابات البث، تكون سليمة ما دام الإنهاء ينفذ فعلاً. نمط الفشل الشائع هو استثناء أو عودة مبكرة توقف الكتابة قبل اكتمال التذييل، فتترك ملفًا يفتحه بعض برامج العرض لأن لديها أساليب استرداد، ويفشل في أخرى لأنها لا تملكها.
يفرض PDF/A وPDF/UA قيودًا إضافية على شجرة الصفحات فوق ما تتطلبه المواصفة الأساسية، لكن أيٌّ منهما لا يخفف شرط /Pages. أي مدقق يتحقق من مطابقة ISO 19005 أو ISO 14289 سيلتقط غياب قاموس Pages بوصفه مخالفة للمواصفة الأساسية قبل أن يصل حتى إلى قواعد الملف الشخصي.