الأدفاق والمرشِّحات
ISO 32000-2 §7.4 Evidence: Standard-backed
لمحة سريعة
قسم بعنوان «لمحة سريعة»معظم بايتات ملف PDF الحقيقي تقع داخل الأدفاق: محتوى الصفحات، والخطوط، والصور، ودفق الإحالة المرجعية نفسه. ونادرًا ما تُخزَّن هذه البايتات خامًا؛ فهي تمرّ أولًا عبر مرشِّح واحد أو أكثر. تتناول هذه الصفحة المرشِّحات التي تصادفها، والغرض من كلٍّ منها، ومواضع تعثُّرها، ولماذا يثبِّت NextPDF ضغطه بحيث يُنتج المُدخل نفسه البايتات نفسها دائمًا.
لماذا يهمّ هذا
قسم بعنوان «لماذا يهمّ هذا»الدفق ومرشِّحه عقدٌ بينهما: “هذه البايتات مضغوطة بـ deflate، ثم مرمَّزة بـ base-85 — فكّ الترميز بهذا الترتيب للحصول على البيانات الحقيقية.” إذا خالف مدخل /Filter حقيقة البايتات، أو كانت قيمة /Length خاطئة، أو أُدرج مرشِّحان بترتيب خاطئ، أصبح الدفق غير قابل لفكّ الترميز وفُقد الكائن الذي كان يحمله. لا يخمِّن القارئ استدلاليًّا؛ بل يفعل ما يمليه عليه القاموس.
وهناك كلفة ثانية أقل ظهورًا. إذا كان ضاغط المكتبة غير حتميّ — إصدار zlib مختلف، أو مستوى مختلف، أو حدود كتل داخلية مختلفة — فإن تشغيلين كان يُفترض أن يُنتجا ملف PDF متطابقًا يُنتجان ملفّين مختلفين. وهذا يكسر قابلية إعادة الإنتاج على مستوى البايت. وكسر قابلية إعادة الإنتاج يكسر بدوره اختبارات الملف الذهبي، والتحقق من البناء الموقَّع، وأيّ خطّ معالجة يقارن المُخرجات بالفروق. لذلك تحدِّد المرشِّحات ما إذا كان ملف PDF صحيحًا وما إذا كان مطابقًا في الوقت نفسه.
النسخة المختصرة
قسم بعنوان «النسخة المختصرة»- كائن الدفق هو قاموس مضافًا إليه كتلة من البايتات، محصورة بين
stream…endstream، ومعها/Lengthوعادةً/Filter. - يسمِّي مدخل
/Filterمرشِّح فكّ الترميز — أو مصفوفة من المرشِّحات تعمل كـ خطّ معالجة بالترتيب. - تنقسم المرشِّحات إلى عائلتين: الضغط (FlateDecode وLZWDecode وRunLengthDecode وDCTDecode وJPXDecode وJBIG2Decode) والنقل بترميز ASCII (ASCIIHexDecode وASCII85Decode)، إضافةً إلى مرشِّح Crypt الخاص بالتشفير.
- والمرشِّح الذي ستراه أكثر من غيره هو FlateDecode — zlib/deflate. وهو الافتراضي للمحتوى والخطوط ودفق الإحالة المرجعية.
- يثبِّت NextPDF ناتج Flate على مستوى وتنسيق ثابتين بحيث تُضغط بايتات المُدخل نفسها دائمًا إلى بايتات المُخرج نفسها.
كيف يتعامل NextPDF مع ذلك
قسم بعنوان «كيف يتعامل NextPDF مع ذلك»يُصدر NextPDF كائنات الدفق عبر مساعد تخزين مؤقت واحد، ويضغط عبر ضاغط مثبَّت واحد — وذلك عن قصد.
تغلِّف BinaryBuffer::writeStream() (src/Support/BinaryBuffer.php) محتوى الدفق داخل قاموسه، فتكتب دائمًا قيمة /Length مساوية لطول البايتات الفعلي، وتدمج أيّ إدخالات إضافية يوفِّرها المستدعي، مثل /Filter. ولا يوجد مسار يمكن فيه أن يتعارض الطول المُعلَن مع البايتات المكتوبة، لأن الطول يُؤخذ من سلسلة المحتوى نفسها.
يجري الضغط عبر PinnedZlibCompressor (src/Writer/PinnedZlibCompressor.php). يوجد هذا الصنف لسبب واحد. إن استخدام gzcompress دون مستوى صريح يعتمد على القيمة الافتراضية في زمن تشغيل zlib، وهي قيمة تباينت تاريخيًّا بين الإصدارات. بل إن ترويسة zlib المؤلَّفة من بايتين ترمِّز المستوى ترميزًا غير مباشر، فلا يكون “الافتراضي” ناتجًا مستقرًّا. يثبِّت الضاغط المستوى عند الحدّ الأقصى لـ RFC 1951 ويُصدر دائمًا deflate مغلَّفًا بـ zlib (ترويسة RFC 1950 + ذيل Adler-32)، وهو بالضبط ما يتوقّعه /Filter /FlateDecode. ويتحوّل الفشل الصريح من zlib إلى استثناء مُحدَّد النوع بدلًا من الرجوع صامتًا إلى ناتج غير مضغوط — فلا يُصدَر دفق خامًا في صمت أبدًا.
دفق الإحالة المرجعية نفسه مثالٌ تطبيقيّ على كلّ ذلك: يبني CrossReferenceStream (src/Core/CrossReferenceStream.php) جدولًا ثنائيًّا، ويضغطه، ويُصدره ككائن دفق يحمل /Type /XRef، ومصفوفة عرض الحقول /W، و/Filter /FlateDecode. فالفهرس الذي يتيح للقارئ العثور على كلّ كائن هو، بدوره، دفق مُرشَّح.
| المرشِّح | العائلة | الغرض منه | موضع تعثُّره |
|---|---|---|---|
| FlateDecode | ضغط | zlib/deflate؛ الافتراضي للمحتوى والخطوط وأدفاق xref | إصدار zlib غير الحتميّ يجعل ملفات PDF “المتطابقة” تختلف بايتًا ببايت |
| LZWDecode | ضغط | ضغط Lempel–Ziv–Welch الأقدم | قديم؛ حلّ Flate محلّه، ولا يزال يُرى أحيانًا في الملفات القديمة |
| DCTDecode | ضغط | صور colour/grayscale مرمَّزة بـ JPEG | فاقد — إعادة ترميز صورة مرمَّزة بـ DCT سلفًا تُتلفها مجدَّدًا |
| JPXDecode | ضغط | بيانات صور JPEG 2000 المويجية | غير مسموح به في بعض الملامح الأرشيفية؛ والدعم الواسع له متفاوت |
| JBIG2Decode | ضغط | ضغط الصور ثنائية المستوى (بت واحد) | يجب ألّا يُستخدم مع الصور المضمَّنة؛ والأوضاع الفاقدة قد تُغيِّر المستندات الممسوحة |
| RunLengthDecode | ضغط | ترميز طول التشغيل على مستوى البايت | لا يفيد إلا البيانات ذات سلاسل البايت الواحد الطويلة؛ وقد يزيد حجم البيانات الأخرى |
| ASCIIHexDecode | نقل | البيانات الثنائية على هيئة أرقام ست عشرية | يضاعف الحجم؛ للقنوات الآمنة لـ 7 بتات فقط، لا للحجم أبدًا |
| ASCII85Decode | نقل | البيانات الثنائية على هيئة ASCII بترميز base-85 | عبء إضافي بنحو 25%؛ تسهيل للنقل، لا ضغط |
| Crypt | أمان | يطبِّق معالج أمان المستند | يجب على دفق الإحالة المرجعية ألّا يستخدم مرشِّح Crypt |
مجموعة مرشِّحات PDF القياسية، حسب العائلة، مع الإخفاق المرتبط كلٌّ منها. يكتب NextPDF ترميز FlateDecode للمحتوى والخطوط ودفق الإحالة المرجعية؛ ومرشِّحات النقل بترميز ASCII للقنوات ذات 7 بتات، لا لتقليل الحجم أبدًا.
ما تقوله الأدلّة
قسم بعنوان «ما تقوله الأدلّة»آلية المرشِّح معرَّفة في Spec: ISO 32000-2, §7.4 ISO 32000-2 §7.4 . يسمِّي قاموس الدفق مرشِّحاته عبر /Filter. عندما يُدرج المدخل أكثر من مرشِّح، تشكِّل تلك المرشِّحات خطّ معالجة لفكّ الترميز وتُطبَّق بالتتابع. يرمِّز الكاتب الدفق لضغطه أو لجعله آمنًا لـ 7 بتات. ويستدعي القارئ مرشِّحات فكّ الترميز المقابلة لاستعادة البيانات الأصلية. Evidence: Standard-backed
يصنِّف جدول المرشِّحات في المعيار كلَّ مرشِّح. يفكّ FlateDecode ضغط البيانات المرمَّزة بـ zlib/deflate-encoded، فيعيد إنتاج النص الأصلي أو البيانات الثنائية الأصلية. ويعيد DCTDecode إنتاج عيِّنات صورة تقارب الأصل عبر JPEG — وكلمة “تقارب” هي تنبيه المعيار لك إلى أنه فاقد. وكلٌّ من LZWDecode وRunLengthDecode وJBIG2Decode وJPXDecode ومرشِّح Crypt معرَّف هناك أيضًا، مع منع JBIG2 صراحةً من الصور المضمَّنة.
يطبِّق دفق الإحالة المرجعية آلية التنسيق نفسها على نفسه: فهو كائن دفق (/Type /XRef،
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) تذكر مصفوفة /W فيه
عرض البايتات لكلّ حقل مدخل في الدفق بعد فكّ ترميزه. ويشترط
المعيار ألّا يكون مشفَّرًا وألّا يستخدم مرشِّح Crypt.
يتّبع CrossReferenceStream في NextPDF هذا بدقّة — FlateDecode،
و/W صريحة، ودون تشفير.
مثال عمليّ
قسم بعنوان «مثال عمليّ»دفق محتوى صفحة، مضغوط بـ Flate. هذا هو الشكل الأكثر شيوعًا بفارق كبير: قاموس به /Length و/Filter، ثم البايتات المضغوطة بين stream وendstream.
<?php
declare(strict_types=1);
use NextPDF\Writer\PinnedZlibCompressor;
// The marking operators a page content stream carries, uncompressed.$content = "BT /F1 12 Tf 72 712 Td (Hello) Tj ET\n";
// NextPDF compresses through the pinned compressor: fixed level,// fixed zlib-wrapped format. The same $content always yields the// same $compressed bytes, on any supported PHP/zlib build.$compressed = PinnedZlibCompressor::compress($content);
// Emitted as a stream object. /Length is the real byte length of// $compressed; /Filter names the decode the reader must apply.// N 0 obj// << /Length <strlen($compressed)> /Filter /FlateDecode >>// stream// <$compressed bytes>// endstream// endobjيفعل القارئ العكس: يقرأ بايتات /Length، ويمرِّرها عبر FlateDecode لأن /Filter يقول ذلك، فيستعيد المعامِلات الأصلية. وبما أن الضاغط مثبَّت، لا تكون رحلة الذهاب والإياب صحيحةً فحسب، بل تكون مطابقة في كلّ مرة، وهو ما تعتمد عليه فحوص الملف الذهبي والبناء الموقَّع.
مفهوم خاطئ شائع
قسم بعنوان «مفهوم خاطئ شائع»الفخّ هو معاملة مرشِّحات ASCII على أنها وسيلة ضغط. يجعل ASCIIHexDecode وASCII85Decode الدفق أكبر — نحو الضعف، ونحو 25% على التوالي. وهي موجودة لنقل البيانات الثنائية عبر قناة آمنة للنص ذي 7 بتات فقط، لا لتوفير المساحة. واختيار ASCII85 لـ “تقليص” ملف PDF يفعل العكس. والنصف الثاني من المفهوم الخاطئ ذاته هو الاعتقاد بأن FlateDecode غير فاقد للصور “بلا مقابل”. إن Flate غير فاقد فعلًا، لكن إذا كانت الصورة مرمَّزة سلفًا بـ DCT (JPEG)، فإن تغليفها مجدَّدًا أو إعادة ترميزها عبر مرشِّح فاقد يُتلفها بصرف النظر عمّا يفعله Flate من حولها. يحفظ خطّ معالجة المرشِّحات ما تُدخله إليه بالضبط — بما في ذلك أيّ أثر لإعادة ضغط أدخلته إليه عرَضًا.
الحدود والقيود
قسم بعنوان «الحدود والقيود»تتناول هذه الصفحة كيفية الإعلان عن المرشِّحات وتطبيقها، لا تفاصيل الخوارزمية على مستوى البتات داخل كلٍّ منها. ضمان الحتمية يخصّ تحديدًا ناتج Flate في NextPDF للأدفاق التي يكتبها. وهو يصمد عبر إصدارات PHP الثانوية وإصدارات zlib المطابقة للمعايير، لكن المعيار يسمح صراحةً لمرمِّز deflate باختيار حدود كتل داخلية مختلفة، لذا فإن الناتج المتطابق بايتًا ببايت عبر تطبيقات zlib المختلفة فعلًا (مثل zlib القياسي مقابل zlib-ng) ليس مضمونًا. ولهذا السبب تُثبَّت بيئة البناء.
يختار NextPDF ترميز FlateDecode ومرشِّحات النقل بترميز ASCII للبيانات التي يُصدرها. وهو ليس مُحوِّل ترميز صور. وهو لا يعِد بإعادة تعبئة أيّ دفق JPEG2000 أو JBIG2 وارد أيًّا كان شكله، والمفاضلات في الصور الفاقدة سمةٌ من سمات بيانات المصدر، وليست شيئًا يستطيع الكاتب التراجع عنه.
أسئلة شائعة موجزة
قسم بعنوان «أسئلة شائعة موجزة»لماذا يوجد FlateDecode في كلّ مكان؟ لأنه غير فاقد، وعامّ الغرض، ومدعوم جيدًا، ومناسب لمحتوى النصوص والمعامِلات في معظم ملفات PDF. وهو الافتراضي الآمن لأدفاق المحتوى والخطوط المضمَّنة ودفق الإحالة المرجعية.
هل يمكنني إيقاف الضغط؟ يمكنك حذف /Filter وتخزين البايتات خامًا، وسيقبلها القارئ. سيكبر الملف، ولن يتحسّن أي شيء آخر؛ ونادرًا ما يوجد سبب لذلك خارج تنقيح الأخطاء.
لماذا نثبِّت مستوى الضغط من الأساس؟ لكي يكون الناتج قابلًا لإعادة الإنتاج. قد يغيِّر المستوى غير المثبَّت (أو إصدار zlib) البايتات المضغوطة دون تغيير المحتوى بعد فكّ الضغط — فيبقى صحيحًا، لكنه غير مطابق، وهو ما يُبطل التحقق على مستوى البايت.
مستندات ذات صلة
قسم بعنوان «مستندات ذات صلة»- ما هو ملف PDF حقًّا — نموذج الكائنات الذي تقع داخله أدفاق هذه الصفحة.
- الخطوط: الجزء الصعب — برامج الخطوط المضمَّنة أدفاق مُرشَّحة، ولها أنماط إخفاقها الخاصة.
- PDF 2.0: ما الذي تغيَّر — كيف يتعامل أساس 2.0 مع الأدفاق ودفق الإحالة المرجعية الذي يفترضه NextPDF.
مسرد المصطلحات
قسم بعنوان «مسرد المصطلحات»- كائن الدفق — قاموس مع كتلة من البايتات بين
streamوendstream، يحمل/Lengthوعادةً/Filter. - المرشِّح — تحويل فكّ ترميز مُسمّى يطبِّقه القارئ على بايتات الدفق (مثل
FlateDecode). - خطّ معالجة المرشِّحات — مصفوفة من المرشِّحات تُطبَّق بالتتابع؛ وترتيب المصفوفة هو ترتيب فكّ الترميز.
- FlateDecode — مرشِّح zlib/deflate؛ الضغط الافتراضي للمحتوى والخطوط وأدفاق الإحالة المرجعية.
- DCTDecode — مرشِّح صور JPEG؛ فاقد، فإعادة الترميز تُتلف الصورة مجدَّدًا.
- مرشِّح النقل بترميز ASCII — ASCIIHexDecode / ASCII85Decode؛ يجعل البيانات آمنة لـ 7 بتات على حساب الحجم — وليس ضغطًا.
- الضغط الحتميّ — إنتاج ناتج مضغوط متطابق بايتًا ببايت للمُدخل المتطابق، يتحقّق بتثبيت مستوى الضاغط وتنسيقه.