Technical Article

النموذج المنطقي لكائنات PDF: الأنواع والمراجع والبنية

ملف PDF هو، في جوهره، مجموعة من الكائنات تشير إلى بعضها بعضا. إذا أزلت الضغط، ودفتر xref، وإزاحات البايتات، فالمتبقي هو رسم بياني: مجموعة صغيرة من القيم المعلّمة، موصولة بمراجع، ومتمركزة حول كائن واحد يعرف القارئ كيف يجده. كل ما يستطيع PDF التعبير عنه، من فقرة نص إلى خط مضمّن إلى توقيع رقمي، يُبنى من ثمانية أنواع أولية من الكائنات ومن القاعدة التي تسمح لكائن بالإشارة إلى آخر. تعلّم هذه القواعد، ويصبح بقية التنسيق تركيباً لا لغزاً.

هذه هي الطبقة المنطقية في PDF، كما يعرّفها ISO 32000-1 clause 7.3، وهي تقع مستوى فوق التخطيط الفيزيائي للملف، أي الرأس والجسم وجدول xref والتذييل، وهو موضوعه الخاص في النظرة التقنية العامة لبنية ملف PDF. النموذج المنطقي هو ما تعنيه تلك البايتات بعد تحليلها. يقرأ العارض الملف من النهاية ليعثر على التذييل، ثم يتبعه إلى الجذر، ومن هناك ينفتح المستند على هيئة كائنات تشير إلى كائنات. هذه هي الطبقة التي تفكر فيها عندما تشخّص صفحة مشوّهة، أو تكتب محللاً، أو تثق بمكتبة لتجميع مستند.

ثمانية أنواع من الكائنات، ولا شيء غير ذلك

يعرّف PDF بالضبط ثمانية أنواع أساسية من الكائنات. كل قيمة في المستند هي واحد منها، وهذا ما يبقي التنسيق قابلاً للإدارة على الرغم من اتساعه.

القيم المنطقية هي الكلمتان المفتاحيتان true وfalse. وهما تشغلان الأعلام وتطفئانها، مثل ما إذا كانت التعليقة التوضيحية ستُطبع.

الأعداد تأتي في شكلين تتعامل معهما المواصفة على أنهما نوع واحد: أعداد صحيحة مثل 42 وأعداد حقيقية مثل 3.14 أو -0.002. لا يملك PDF ترميز الأسّ، لذلك لن ترى أبدا 1e6 في ملف مطابق للمواصفات. الإحداثيات وأحجام الخطوط وزوايا الدوران كلها أعداد.

السلاسل النصية تحمل تسلسلات من البايتات، وتكتب إما بين قوسين، (Hello)، أو بين زوايا على شكل سداسي عشري، <48656C6C6F>. كلا الترميزين يحمل المحتوى نفسه؛ والهيكس هو المخرج الاحتياطي للبايتات التي يصعب وضعها داخل الأقواس. السلاسل تحمل نصاً، لكنها بايتات أولاً، وهذا مهم فور أن تتعامل مع أي شيء يتجاوز ASCII.

الأسماء هي رموز ذرية يسبقها شرطة مائلة: /Type، /Pages، /MediaBox. الاسم ليس سلسلة نصية؛ إنه معرّف، يُستخدم مفتاحاً في قاموس أو قيمة معدودة، واسمان لا يتساويان إلا إذا تطابقا بايتاً لبايت. الشرطة المائلة هي من البنية النحوية، وليست جزءاً من الاسم. وهذا يربك القادمين الجدد الذين يعاملون /Times-Roman والسلسلة (Times-Roman) على أنهما قابلان للتبادل؛ التنسيق لا يفعل ذلك.

المصفوفات هي قوائم مرتبة غير متجانسة بين أقواس مربعة: [0 0 612 792] هو مستطيل صفحة، ويمكن للمصفوفة أن تخلط الأنواع بحرية، بما في ذلك المراجع إلى كائنات أخرى. القواميس هي العمل الخشن الأساسي. تُكتب بين << و>>، وهي تربط مفاتيح الأسماء بقيم من أي نوع، وتقريباً كل بنية ذات معنى في PDF، الصفحة، Catalog، الخط، التعليقة التوضيحية، هي قاموس يحتوي على مفتاح /Type يعلن ما هو.

التيارات هي قواميس يتبعها ذيل من البايتات الخام بين الكلمتين المفتاحيتين stream وendstream. يصف القاموس البايتات، أي طولها وأي عوامل ترشيح مثل FlateDecode تضغطها، بينما تحمل البايتات الحمولة الثقيلة: تعليمات محتوى الصفحة، برامج الخطوط المضمّنة، الصور. التيار هو الموضع الذي يضع فيه PDF أي شيء كبير جدا أو ثنائي جدا ليوضع داخل السطر.

النوع الثامن هو الكائن null، أي الكلمة المفتاحية null. إنه قيمة حقيقية، منفصلة عن غياب المفتاح. أي مدخل في قاموس يُضبط على null يُعامل كما لو أنه غير موجود، وأي مرجع يحل إلى كائن غير موجود يعطي أيضاً null بدلاً من خطأ. هذا السلوك المتساهل مقصود: إنه يسمح للملف التالف أن يتدهور بدل أن يرفض الفتح. لا يوجد نوع تاسع؛ كل ما يعبر عنه PDF يأتي من كيفية اجتماع هذه الأنواع الثمانية.

قيم مباشرة وكائنات غير مباشرة ومراجع

يمكن لأي من تلك الأنواع الثمانية أن يظهر بطريقتين. الكائن المباشر يُكتب في موضعه، مثل 612 داخل مصفوفة MediaBox. أما الكائن غير المباشر فيُعطى هوية حتى تتمكن الكائنات الأخرى من الإشارة إليه: عددان صحيحان، رقم الكائن ورقم الجيل، يحيطان بالتعريف داخل obj وendobj:

12 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj

هذا هو الكائن 12، الجيل 0، قاموس خط. وفي أي موضع آخر من الملف، يشير إليه كائن آخر عبر مرجع غير مباشر: العددان نفسيهما يتبعهما الحرف R، أي 12 0 R. المرجع هو مؤشّر. عندما يقول قاموس موارد صفحة /Font << /F1 12 0 R >>، فهو يسمّي الكائن 12 بوصفه الخط الكامن وراء اسم المورد /F1، من دون نسخ تعريف الخط إلى الصفحة.

يوجد رقم الجيل من أجل الحذف وإعادة الاستخدام. عندما يُحرَّر كائن ويُعاد استخدام موضعه، يزداد رقم الجيل حتى لا يتمكن 12 0 R قديم من الحل إلى المستأجر الجديد للموضع 12. الملفات المكتوبة حديثاً تكون كلها تقريباً عند الجيل 0، لكن الملف الذي عُدّل بكثافة قد يحمل أرقاماً أعلى، والمحلل الذي يتجاهل رقم الجيل سيقرأ في النهاية الكائن الخطأ.

الترميز غير المباشر هو ما يجعل PDF فعالاً وقابلاً للتحرير. يمكن تعريف خط واحد أو صورة أو مساحة ألوان مرة واحدة، ثم الإشارة إليه من مئة صفحة. ويمكن إلحاق تغيير صغير كمراجعة جديدة تتجاوز كائناً واحداً بدلاً من إعادة كتابة الملف. جدول xref هو الفهرس الذي يحول رقم الكائن إلى إزاحة بايت، فيقفز القارئ مباشرة إلى 12 0 obj من دون مسح، لكن هذا تحسين فيزيائي. من الناحية المنطقية، كل ما تحتاج إلى معرفته هو أن 12 0 R تعني "الكائن المحدد على أنه 12 0".

Catalog: حيث يبدأ كل مستند

لا بد أن يبدأ حل المراجع من مكان ما، وذلك المكان هو مدخل /Root في التذييل، وهو يشير إلى Catalog الخاص بالمستند: جذر الرسم البياني للكائنات، أي قاموس يضم /Type /Catalog. يصل إليه القارئ أولاً لأن التذييل يُعثر عليه أولاً، ومن هناك يمكن الوصول إلى كل جزء آخر من المستند عبر تتبع المراجع.

يحمل Catalog مدخلين مطلوبين فقط على نحو صارم: /Type، و/Pages، وهو مرجع غير مباشر إلى جذر شجرة الصفحات. أما البقية فهي اختيارية وتصف سلوكاً على مستوى المستند كله لا المحتوى: /Outlines يشير إلى شجرة الإشارات المرجعية، و/Names يحتفظ بأشجار أسماء مفهرسة بسلاسل نصية، و/Metadata يشير إلى تيار بيانات XMP الوصفية، و/PageMode و/PageLayout يقترحان كيفية فتح العارض للمستند. لا يحتاج أي منها إلى عرض صفحة، لكنها تضبط التجربة المحيطة بالصفحات. وتتناول المقالة حول بيانات PDF الوصفية، والإشارات المرجعية، والتعليقات التوضيحية الهياكل الخاصة بالإشارات المرجعية والبيانات الوصفية والتعليقات التوضيحية المعلّقة من Catalog.

يوضح المخطط أدناه موضع جسم الكائنات داخل الملف المحيط. يعيش Catalog وشجرة الصفحات داخل ذلك الجسم على هيئة كائنات غير مباشرة عادية، أما الرأس وجدول xref والتذييل حولهما فهي البنية الفيزيائية التي تتيح للقارئ تحديد موضعهما.

مخطط يوضح الأقسام الفيزيائية الأربعة لملف PDF: رأس إصدار، وجسم يحمل كائنات المستند بما فيها Catalog وPage tree، وجدول xref لإزاحات الكائنات، وتذييل يشير إلى الجذر

شجرة الصفحات: تسلسل هرمي متوازن للصفحات

من /Pages يتفرع المستند إلى شجرة الصفحات، حيث تؤتي خيارات PDF باختيار رسم بياني بدل قائمة مسطحة ثمارها. لا تُخزن الصفحات كسلسلة بسيطة؛ بل تتدلى من شجرة تكون عقدها الداخلية عقد شجرة الصفحات (/Type /Pages) وتكون أوراقها كائنات الصفحة (/Type /Page). تسرد العقدة الداخلية أبناءها في مصفوفة /Kids، ويسجل /Count عدد الصفحات الورقية الموجودة تحتها. وكل عقدة باستثناء الجذر تحمل مرجع /Parent يعود إلى الأعلى، لذلك يمكن اجتياز الشجرة في أي اتجاه.

2 0 obj                                  % root of the page tree
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 3 >>
endobj

3 0 obj                                  % a leaf page
<< /Type /Page /Parent 2 0 R
   /MediaBox [0 0 612 792]
   /Resources << /Font << /F1 12 0 R >> >>
   /Contents 5 0 R >>
endobj

4 0 obj                                  % an interior node grouping two more pages
<< /Type /Pages /Parent 2 0 R /Kids [6 0 R 7 0 R] /Count 2 >>
endobj

هنا يكون الكائن 2 هو الجذر، وتحته ثلاث صفحات: الصفحة الورقية 3، بالإضافة إلى صفحتين أخريين يمكن الوصول إليهما عبر العقدة الداخلية 4. يجب أن يساوي /Count للجذر القيمة 3 مجموع الأوراق الموجودة تحته، وأي عدٍّ لا يطابق البنية الفعلية هو أحد أكثر الأخطاء شيوعاً في الملفات المحررة يدوياً. الغاية من الشجرة هي قرب الوصول. القارئ الذي يفتح الصفحة 900 من مستند من ألف صفحة لا يجوب 900 كائن، بل يهبط عبر عدد قليل من العقد، لأن الشجرة السليمة تبقى ضحلة ومتوازنة. وبناء مثل هذه الشجرة يدوياً متعب بما يكفي ليكون من المفيد رؤيته من البداية إلى النهاية، وهو ما يفعله الشرح في بناء مستند PDF من الصفر.

تكسب الشجرة فائدتها الثانية عبر الوراثة. يمكن ضبط عدد قليل من خصائص الصفحة، مثل /Resources و/MediaBox و/CropBox و/Rotate، على عقدة داخلية وتركها خارج الصفحات الفردية، فتورث الصفحات قيمة السلف الأقرب. اضبط /MediaBox مرة واحدة على الجذر فتحصل كل ورقة على الحجم نفسه من دون تكرار؛ والصفحة التي تحتاج إلى اختلاف تعلن قيمتها الخاصة. هذا هو الموضع الوحيد في نموذج الكائنات الذي يعتمد فيه معنى قيمة ما على موضع الكائن في الشجرة، لا على محتواه وحده.

ما الذي تحمله صفحة ورقية فعلياً

كائن الصفحة هو نقطة الوصل بين النموذج البنيوي والمحتوى المرئي. مدخله /Contents يشير إلى تيار محتوى واحد أو أكثر، أي أوامر الرسم التي تطبع النص والرسوم على الصفحة. أما قاموس /Resources فيسمّي الخطوط والصور ومساحات الألوان التي تعتمد عليها تلك الأوامر، وكل مدخل فيه مرجع غير مباشر إلى كائن مشترك عبر الصفحات. يحدد /MediaBox مستطيل الصفحة بالنقاط، أي 1/72 من البوصة، بينما تضبط مدخلات مثل /Rotate و/CropBox طريقة عرضه.

هذا التقسيم للعمل هو النموذج كله في صورة مصغرة. قاموس الصفحة هو البنية: مدخلات معلّمة ومراجع تقول ما هي الصفحة وبماذا ترسم. تيار المحتوى هو التعليمات: كتلة منفصلة قابلة للضغط تقول كيف يُرسم. الخط الموجود خلف /F1 مورد مشترك، يُعرّف مرة واحدة وتُشار إليه أينما استُخدم. القاموس والتيار والمرجع تتعاون لعرض صفحة واحدة، وتُطبَّق الأنماط نفسها على المستند كله. وتُغطَّى معاملات تيار المحتوى داخل تلك الكتلة على نحو منفصل في النصوص والخطوط وفي الرسوم والعناصر البصرية.

لماذا يستحق هذا النموذج أن تعرفه

معظم المطورين لا يلتقون نموذج الكائنات إلا عندما يحدث خلل: تظهر الصفحة فارغة لأن مرجع /Contents فيها متدلٍّ، أو يخرج النص على هيئة مربعات لأن مورد خط لم يُضمَّن قط، أو يبلغك أحد الأدوات أن /Count لا يطابق الصفحات التي استطاع العثور عليها. كل واحدة من هذه الحالات هي بيان عن الرسم البياني، وقراءة الرسم البياني مباشرة أفضل من التخمين. الأنواع الثمانية وقاعدة المرجع مفردات صغيرة بما يكفي لتبقى في الذهن، وما إن ترى PDF على أنه كائنات تشير إلى كائنات حتى تتوقف الملفات المشوهة عن أن تكون غامضة.

ومع ذلك، نادراً ما يكون كتابة النموذج يدوياً القرار الصحيح خارج التعلم. الحفاظ على اتساق إزاحات xref، وأرقام الجيل، وعدد صفحات الشجرة، وأطوال التيارات عبر التعديلات هو نوع العمل الإداري الذي وُجدت المكتبة لتتولاه. في الإنتاج، تدير مكتبة تطوير PDF ناضجة الرسم البياني للكائنات بينما تتركك تفكر بالصفحات والمحتوى. ومعرفة النموذج ما زالت مجدية: أنت تفهم ما تبنيه المكتبة في الداخل، ولماذا.