تخطَّ إلى المحتوى

ما حقيقة ملف PDF

Evidence: Standard-backed

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

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

إذا تعاملت مع ⁨PDF⁩ بوصفه صندوقًا مغلقًا، بدت تلك الإخفاقات عشوائية. وإذا عرفت نموذج الكائنات، ظهرت كما هي فعلًا: رقم لا يطابق موضعًا. فهم التنسيق هو الفارق بين “ملف ⁨PDF⁩ تالف” و”إزاحة الكائن 14 قديمة لأن الكاتب قاسها قبل إنهاء طول الدفق.”

يتألف ملف ⁨PDF⁩ من أربعة أجزاء، بترتيب الملف:

  1. ترويسة — سطر واحد يسمّي الإصدار (%PDF-2.0).
  2. جسم — تسلسل من الكائنات غير المباشرة المرقّمة: قواميس، ودفقات، ومصفوفات، وأرقام، وسلاسل نصية، وأسماء.
  3. جدول مراجع متقاطعة (أو، في ⁨PDF 2.0⁩، دفق مراجع متقاطعة) — بحث يربط رقم الكائن بإزاحة البايت، بحيث يمكن الوصول إلى أي كائن دون مسح الملف.
  4. ذيل — قاموس صغير يسمّي الكائن الجذر للمستند ويشير إلى موضع بدء قسم المراجع المتقاطعة.

لا يقرأ القارئ ملف ⁨PDF⁩ من بدايته إلى نهايته. بل يقرأ السطر الأخير أولًا، ويعثر على startxref، ويقفز إلى قسم المراجع المتقاطعة، ثم يستخدمه فهرسًا للجسم. لقد صُمّم التنسيق ليُقرأ من نهايته. وهذه الحقيقة وحدها تفسّر معظم تصميمه.

يبني ⁨NextPDF⁩ ملف ⁨PDF⁩ بالطريقة التي يُقرأ بها التنسيق: الكائن أولًا، ثم تسجيل الإزاحة بعده، ثم كتابة الجدول أخيرًا.

يُخصَّص لكل كائن غير مباشر رقمٌ من سجلّ واحد (src/Core/ObjectRegistry.php). يوزّع السجلّ أرقامًا متتالية عبر allocate()، ويسجّل إزاحة البايت — بعد كتابة بايتات الكائن إلى مخزن الإخراج — عبر register(). لا تُخمَّن الإزاحات مسبقًا أبدًا، بل تُلتقط من BinaryBuffer::getOffset() لحظة إصدار ترويسة الكائن. ولهذا لا يمكن لمدخل المراجع المتقاطعة في ⁨NextPDF⁩ أن ينحرف عن الكائن الذي يصفه: فالإزاحة هي موضع المخزن الفعلي كما هو.

عند اكتمال الجسم، تتولى استراتيجية تسلسل خاصة بالإصدار (src/Writer/PdfSerializationStrategy.php) كتابة قسم المراجع المتقاطعة والذيل:

  • يُصدِر Pdf20StreamStrategy دفق مراجع متقاطعة مضغوطًا (/Type /XRef) — وهو السلوك الافتراضي في ⁨PDF 2.0.⁩
  • يُصدِر Pdf17TableStrategy وPdf14TableStrategy جدول مراجع متقاطعة تقليديًّا بحجم 20 بايت إضافة إلى قاموس ذيل منفصل — وهو ما تتطلبه ملفات تعريف ⁨PDF/A⁩ التي تفرض بنية ملف أقدم.

تُختار الاستراتيجية بحسب ملف الإخراج، لا بالاستنتاج. وأيًّا كانت الاستراتيجية، فإن البايتات النهائية تأخذ الشكل ذاته: قسم المراجع المتقاطعة، ثم startxref، ثم إزاحة البايت، ثم %%EOF. وهذا الذيل هو ما يجده القارئ أولًا.

  1. Step 1 of 4: ISO 32000-2 §7.5.5 — %%EOF and startxref at the file end
  2. Step 2 of 4: ISO 32000-2 §7.5.4 / §7.5.8 — the cross-reference section maps object number to offset
  3. Step 3 of 4: ISO 32000-2 §7.5.5 — the trailer names /Root, the document catalog
  4. Step 4 of 4: ISO 32000-2 §7.3.10 — each indirect object is reached at its recorded offset
كيف يحلّ القارئ كائنًا داخل ملف NextPDF، وبند ISO 32000-2 الذي يحدّد كل خطوة: يبدأ من نهاية الملف ويعمل باتجاه الداخل.

البنية رباعية الأجزاء ليست عُرفًا خاصًّا بـNextPDF؛ بل هي جزء من بنية الملف في Spec: ISO 32000-2, §7.5 . يعرّف المعيار ملف PDF بأنه ترويسة، وجسم من الكائنات، وجدول مراجع متقاطعة، وذيل، وينصّ على أن القارئ ينبغي أن يحلّل الملف من نهايته. السطر الأخير هو %%EOF، والسطران السابقان له هما الكلمة المفتاحية startxref وإزاحة البايت إلى قسم المراجع المتقاطعة.

Evidence: Standard-backed

يُعرَّف الكائن غير المباشر بأنه رقم كائن ورقم جيل، يفصل بينهما فراغ، تليهما قيمة الكائن محصورة بين الكلمتين المفتاحيتين obj وendobj. يحدّد الجمع بين رقم الكائن ورقم الجيل الكائنَ على نحو فريد؛ وتُكتب الإشارة غير المباشرة إليه برقم الكائن، ورقم الجيل، والكلمة المفتاحية R. يحاكي ObjectRegistry الخاص بـ⁨NextPDF⁩ هذا تمامًا: رقم متتالٍ، والجيل 0 للكائنات المكتوبة حديثًا، وإزاحة مسجَّلة.

يسمح ⁨PDF 1.5⁩ فما بعده أيضًا بأن توجد الكائنات داخل دفق كائنات، حيث تُخزَّن من دون الكلمتين المفتاحيتين obj/endobj ويجب أن يكون جيلها صفرًا. إن دفق المراجع المتقاطعة (/Type /XRef، Spec: ISO 32000-2, §7.5.8 ) هو آلية PDF 2.0 التي تفهرس الكائنات العادية وهذه الكائنات المضغوطة معًا. يبني CrossReferenceStream الخاص بـ⁨NextPDF⁩ ذلك باستخدام مصفوفة عرض حقول /W و ضغط ⁨FlateDecode.⁩

هذا هو شكل أبسط جسم ⁨PDF⁩ مع ذيله. الأرقام في قسم المراجع المتقاطعة هي إزاحات بايت. يجب أن تكون صحيحة تمامًا، ولهذا يسجّلها ⁨NextPDF⁩ من المخزن بدلًا من حسابها.

%PDF-2.0
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>
endobj
xref
0 4
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000122 00000 n
trailer
<< /Size 4 /Root 1 0 R >>
startxref
196
%%EOF

يفتح القارئ هذا الملف من الأسفل: %%EOF، ثم startxref 196، ثم يقفز إلى البايت 196 حيث يبدأ xref، فيقرأ أن الكائن 1 يقع عند البايت 9، ويتبع /Root 1 0 R إلى الفهرس، ثم يسير في شجرة الصفحات من هناك. الكائن 0 هو دائمًا رأس قائمة الكائنات الحرة بالجيل 65535 — وهي خصوصية موروثة من أقدم تصاميم التنسيق، تُعاد بأمانة لأن القرّاء يتوقعونها.

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

تصف هذه الصفحة بنية الملف، لا محتوى الصفحة. أما كيفية وصول العلامات إلى الصفحة — دفقات المحتوى، ومُعامِلات الرسومات، وعرض النص — فهي موضوع منفصل. كما لا تتناول ما يحدث عندما يُغيَّر ملف بعد كتابته. فتلك مهمة التحديثات التزايدية، حيث يُلحق الكاتب قسم مراجع متقاطعة ثانيًا ويتسلسل الذيل إلى الوراء.

⁨NextPDF⁩ هو كاتب. والسلوك الموصوف هنا هو كيفية تسلسله لمستند بناه. إنه ليس محلّل ⁨PDF⁩ عام الأغراض ولا أداة إصلاح. وهو لا يَعِد بقراءة ملف عشوائي من طرف ثالث بجدول مراجع متقاطعة تالف أو إعادة بنائه أو إنقاذه. الضمان ضيّق ومقصود. الملفات التي يكتبها ⁨NextPDF⁩ لها إزاحات متطابقة، لأنها مقاسة لا متوقعة.

لماذا أرقام الأجيال إذا كانت الملفات الجديدة تستخدم 0 دائمًا؟ توجد أرقام الأجيال لإعادة استخدام الكائنات عبر التحديثات. في الملف المكتوب حديثًا يكون كل كائن عند الجيل 0. ولا تظهر الأجيال غير الصفرية إلا عندما يُحدَّث ملف تزايديًّا ويُعاد تدوير رقم كائن.

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

هل يهم ترتيب الكائنات في الملف بالنسبة إلى الإخراج؟ لا. يكتب ⁨NextPDF⁩ الكائنات بترتيب حتمي لبناءات قابلة لإعادة الإنتاج، لكن القارئ يحلّ كل شيء عبر قسم المراجع المتقاطعة، لذلك لا يحمل الترتيب الفعلي معنى دلاليًّا.

  • الكائن غير المباشر — كائن مرقّم في الجسم، يُكتب على هيئة N G obj … endobj، حيث N رقم الكائن وG رقم الجيل.
  • الإشارة غير المباشرة — مؤشّر إلى كائن غير مباشر، يُكتب N G R.
  • جدول المراجع المتقاطعة (⁨xref⁩) — الفهرس من رقم الكائن إلى إزاحة البايت. في ⁨PDF 2.0⁩ يكون هذا عادةً دفق مراجع متقاطعة (/Type /XRef) بدلًا من الجدول النصي الكلاسيكي بحجم 20 بايت لكل مدخل.
  • الذيل — القاموس في نهاية قسم المراجع المتقاطعة الذي يسمّي /Root (فهرس المستند) و/Size، ويُعثَر عليه عبر إزاحة startxref.
  • دفق الكائنات — كائن دفق يحتوي بنفسه على كائنات غير مباشرة أخرى (مضغوطة معًا)؛ ليس لأعضائه obj/endobj وجيلها صفر.
  • فهرس المستند — الكائن الذي يسمّيه /Root؛ نقطة الدخول إلى شجرة الصفحات وسائر أجزاء المستند.