ينتج مكوّن PDFium ملفات PDF بعرض Fast Web View (خطية) من Delphi عبر TPdf.SaveAsLinearized. تحفظ الطريقة المستند الحالي عبر PDFium، ثم تعيد كتابة النتيجة وفق الملحق F من ISO 32000-1: كائنات الصفحة الأولى تنتقل إلى المقدمة، ويُكتب قسما مراجع متقاطعة، وتخبر جداول تلميحات إزاحة الصفحات والكائنات المشتركة العارضَ أيّ نطاقات بايت يجلبهاء لأي صفحة
سبب اهتمام أي أحد هو الشبكة. كتالوج بحجم 40 MB يقدَّم من مخزن كائنات أو بوابة مستندات ينبغي أن يعرض صفحته الأولى بعد أول بضعة كيلوبايتات، والقفز إلى الصفحة 380 ينبغي أن يكلف زوجاً من طلبات HTTP Range لا تنزيلاً كاملاً. لا تستطيع العارضات ذلك إلا عندما يكون الملف خطياً. أغلب ملفات PDF الخارجة من خط تحرير ليست كذلك، لأن الطريقة الطبيعية لكتابة PDF تضع جدول المراجع المتقاطعة في النهاية وتبعثر كائنات الصفحات حيثما صادف الكاتب أن يُصدرها
لماذا يقرأ PDFium الملفات الخطية ولا يكتبها؟
لدى PDFium قارئ كامل للـ linearization ولا كاتب لها. تكشف واجهة التوفر FPDFAvail_* قاموس الـ linearization، وتفك جداول التلميحات، وتطلب من المضيف نطاقات البايت التي تحتاجها بالضبط، وعلى ذلك بُني سير التنزيل التدريجي والإلغاء في مكوّن PDFium. أما واجهة ABI العامة للحفظ فلا تقدم سوى حفظ نسخ عادي وحفظ بنسخة PDF هدف. ولا شيء فيها يعيد ترتيب الكائنات أو يُصدر تدفقات تلميحات
يسد مكوّن PDFium تلك الفجوة دون إعادة تنفيذ ما يجيده PDFium أصلاً. يستدعي TPdf.SaveAsLinearized أولاً الـ SaveAs الموجود، فيبقى PDFium مالك الحفظ الدلالي: الصفحات المحررة وحالة حقول النماذج ومعالجة النسخ كلها تمر عبر مسار الكود المُجرَّب منذ سنوات. ثم يجري المكوّن مرحلة إعادة هيكلة وفق الملحق F على ملف PDF الوسيط المستقر ويكتب النتيجة الخطية إلى هدفك. شطر العمل هكذا يعني أن الـ linearization لا تحتاج أبداً أن تفهم تعليقاً توضيحياً أو نموذجاً، بل الكائنات والمراجع وإزاحات البايت فقط
استدعاء TPdf.SaveAsLinearized من Delphi
لدى TPdf.SaveAsLinearized تحميلان زائدان، أحدهما لـ TStream والآخر لاسم ملف، وكلاهما يأخذ المعاملين الاختياريين نفسهما Option: TSaveOption و PdfVersion: TPdfVersion كما في SaveAs. ويعيد كلاهما True عند النجاح. يكتب التحميل الملفي إلى ملف مؤقت في مجلد الهدف ويستبدل الوجهة ذرياً، فلا يترك حفظ فاشل أبداً نصف ملف PDF مكتوب حيث كان ملف جيد
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'catalog-2026.pdf';
Pdf.Active := True;
// ... حرر الصفحات واملأ النماذج وألصق الأختام، وأي شيء يفعله خط الأنابيب ...
if not Pdf.SaveAsLinearized('catalog-2026-web.pdf', saNoIncremental, pv17) then
raise Exception.Create('Linearized save failed');
finally
Pdf.Free;
end;
end;
قواعد المعاملات صارمة عمداً. يرفع saIncremental خطأً، لأن التحديث التزايدي يلحق مراجعة جديدة بآخر الملف فيدمّر فوراً التخطيط المحمَّل في المقدمة. وتُرفض أيضاً نسخة هدف أدنى من PDF 1.2؛ ويحفظ pvUnknown نسخة المصدر. الإدخال المشفر يحتاج saRemoveSecurity، لأن الكاتب يعيد ترقيم الكائنات ومفاتيح التشفير على مستوى الكائن تُشتق من رقم الكائن. ولأن الـ linearization إعادة كتابة كاملة، فأي توقيع رقمي موجود يُبطل. وإذا اضطررت لتسليم ملف موقَّع وخطي، فسطّره أولاً ثم وقّعه بعد ذلك
كيف يجمع الكاتب الكائنات لعرض Fast Web View
الـ linearization ليست مجرد نقل الصفحة الأولى إلى رأس الملف. يحلل الكاتب المراجعة الحالية، سواء كانت بيانات مراجعها المتقاطعة جدولاً كلاسيكياً أم ملفاً هجيناً أم تدفق مراجع متقاطعة، ويوسّع كل عضو في تدفق كائنات نشط إلى كائن مستقل. ثم يبني مخطط مراجع ويرتب الكائنات في خمس مجموعات: كائنات المستند اللازمة لفتح الملف، وكائنات الصفحة الأولى، والكائنات الخاصة بصفحة لاحقة واحدة، والكائنات المشتركة بين عدة صفحات لاحقة، وكل شيء آخر
وفخ ذلك السير في المخطط هو /Parent. كل قاموس صفحة يشير صعوداً في شجرة الصفحات، واجتياز ساذج يتبع ذلك المؤشر إلى العقدة الأم، ثم ينزل عبر /Kids إلى كل صفحة شقيقة وتدفقات محتواها. بعد خطوة واحدة تصبح مجموعة الصفحة الأولى المستند كله، والملف خطي تقنياً لكنه عديم الفائدة للوصول بالصفحة-أولاً. يتجاهل مكوّن PDFium رابط /Parent الخاص بالصفحة وعقد شجرة الصفحات خارج الصفحة التي تُجمع، وهذا ما يبقي قسم الصفحة الأولى صغيراً. على عينة انحدار من 51 صفحة بشجرة صفحات عميقة، قسم الصفحة الأولى 5289 بايتاً، أي 0.48% من الخرج البالغ 1105354 بايتاً
تدفقات الكائنات وتدفقات المراجع المتقاطعة في الإدخال تُسطَّح إلى كائنات غير مباشرة عادية من الجيل 0، وتُعاد كتابة كل مرجع إلى الأرقام الجديدة، ويستخدم الخرج أقسام مراجع متقاطعة كلاسيكية. يكلّف ذلك بعض حجم الملف مقارنة بتدفقات الكائنات المضغوطة، لكنه يبسّط تغطية جداول التلميحات وهو التخطيط الذي تعالجه كل عارض يدرك الـ linearization. وإذا كانت المدمغات أهم من Fast Web View لخرج معين، فلدى مكوّن PDFium كاتب تدفقات كائنات منفصل لتلك المهمة
ماذا يدخل في قاموس الـ linearization وقسمَي xref؟
ملف خطي كتبه مكوّن PDFium يحمل قسمَي مراجع متقاطعة كلاسيكيَين وقاموس linearization في أول كائن على الإطلاق. يتبع قسم xref الأول قاموس الـ linearization ويغطي كائنات المستند والصفحة الأولى، فيستطيع عارض أن يعرض الصفحة الأولى من مقدمة الملف وحدها. ويجلس قسم xref الرئيسي في النهاية ويغطي كل شيء آخر
يحمل قاموس الـ linearization نفسه الأرقام التي يحتاجها عارض قبل أن يقرأ أي شيء آخر: /L طول الملف، و /H إزاحة طول تدفق التلميحات، و /O رقم كائن الصفحة الأولى، و /E نهاية قسم الصفحة الأولى، و /N عدد الصفحات، و /T إزاحة أول مدخل في جدول xref الرئيسي. كل ذلك يعتمد على التخطيط النهائي، والقاموس يجلس قبل التخطيط. كتابته بأرقام بعرض طبيعي تخلق حلقة: طول ينمو من 999999 إلى 1000000 يضيف بايتاً، فيزيح كل إزاحة تالية، وهذا يمكن أن يغيّر عرض رقماً آخر، وهكذا
يكسر مكوّن PDFium تلك الحلقة بحجز 320 بايتاً ثابتة لقاموس الـ linearization. تُكتب كل الكائنات وتدفق التلميحات وقسما xref وكلا المقطع الختامي أولاً؛ وبمجرد أن تستقر كل الإزاحات يُملأ القاموس في مكانه ويُحشى إلى العرض المحجوز. وتفصيلة دقيقة هي /T: يجب أن يشير إلى مدخل الكائن 0 في جدول xref الرئيسي، لا إلى كلمة xref التي تسبقه. تعلّم المدققون الصارمون إزاحة الكلمة مع أن العارضات المتسامحة تفتح الملف على أي حال
لماذا تتجاهل إزاحات جداول التلميحات تدفق التلميحات؟
تُفسَّر إزاحات جداول التلميحات في ملف PDF خطي وكأن تدفق التلميحات غير موجود في الملف. يعرّف الملحق F ذلك هكذا حتى يجلس تدفق التلميحات بين قسم الصفحة الأولى والبقية دون أن يغذي حجمه الخاص الأرقام التي يحويها. والنتيجة العملية للكاتب سهلة الخطأ: أي إزاحة حقيقية تقع بعد تدفق التلميحات لا بد أن تُخفَّض قبل دخولها جدول إزاحات الصفحات أو جدول تلميحات الكائنات المشتركة
والسؤال: تخفَّض بمقدار كم؟ الإجابة المغرية هي طول بيانات التدفق، لأنها الجزء الذي يبدو محتوى التلميحات. الإجابة الصحيحة هي طول كائن التلميحات غير المباشر كله، شاملاً N 0 obj وقاموس التدفق وكلمتَي stream و endstream و endobj. اطرح طول البيانات فقط وستكون كل إزاحة لاحقة خاطئة ببضعة عشرات البايتات. ما يزال الصف الأول يُعرض لأنه يعيش قبل تدفق التلميحات، فينجو الخلل من فحص بصري سريع ولا يظهر إلا عندما يقفز عارض إلى صفحة لاحقة ويطلب نطاقاً يبدأ في منتصف الكائن الخطأ
كلا جدولي التلميحات يُحزم بتّياً بالبتة الأعلى معنوية أولاً، وذلك مرة أخرى وفق الملحق F. يتحقق المكوّن من النتيجة بالطريقة التي سيتصرف بها عميل حقيقي: يوفر مخزن نطاقات متناثر أول 1 KiB فقط من عينة الصفحات الـ 51، وتطلب معالجة التلميحات الأصلية في PDFium الباقي. وتفتح الصفحة الأخيرة دون أن يكون الملف الكامل حاضراً قط
التحقق من PDF خطي في Delphi
أرخص فحص هو أن تسأل PDFium نفسه. يشغّل TPdf.InspectAvailability واجهة التوفر على تدفق دون تحميل المستند، ويبلّغ حقل Linearization عنه بـ plnLinearized أو plnNotLinearized أو plnUnknown
function IsFastWebView(const FileName: string): Boolean;
var
Stream: TFileStream;
Info: TPdfAvailability;
begin
Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
Info := TPdf.InspectAvailability(Stream);
Result := Info.Linearization = plnLinearized;
finally
Stream.Free;
end;
end;
ذلك يؤكد أن PDFium يتعرّف على التخطيط؛ ولا يثبت أن التلميحات صحيحة. لاختبار حقيقي من البداية إلى النهاية، قدّم الملف عبر ناقل محدود النطاقات وافتح صفحة متأخرة بـ TPdfProgressiveDocument، بالطريقة نفسها التي يفعلها دليل بث ملفات PDF الكبيرة عند الطلب عبر PDFium VCL. إذا عرضت الصفحة بعد جلب جزء يسير من الملف فقط، فجداول التلميحات تقوم بعملها. وإضافة مدقق linearization ثانٍ مستقل إلى خط الإصدار تستحق أيضاً، لأن العارضات تتفاوت في مدى تسامحها مع /T في غير موضعه أو إزاحة خاطئة قليلاً
حدان يستحقان التذكر. لا يزال مجسِّد الكائنات يرفض مصفوفات من 2 GiB أو أكثر، فالمستندات الضخمة جداً تحتاج استراتيجية مختلفة في الوقت الحالي. ولا تثمر الـ linearization إلا عندما يحترم الخادم طلبات HTTP Range؛ وخلف وسيط يُسقط Accept-Ranges يُنزَّل الملف الخطي تماماً مثل أي ملف آخر
الأسئلة الشائعة
هل يحفظ TPdf.SaveAsLinearized التوقيعات الرقمية صالحة؟
لا. يعيد TPdf.SaveAsLinearized ترقيم كل كائن وكتابته، فتُبطل التوقيعات الموجودة. سطّر أولاً، ثم طبّق التوقيع
هل يمكن لمكوّن PDFium تسطير ملف PDF مشفر؟
فقط عندما تمرر saRemoveSecurity. مفاتيح التشفير على مستوى الكائن تعتمد على أرقام الكائنات، ويغيّرها الكاتب، فيُكتب الإخراج دون تشفير
أي إصدارات PDF يدعمها الإخراج الخطي؟
يقبل TPdf.SaveAsLinearized قيمة pvUnknown للاحتفاظ بنسخة المصدر أو أي هدف من PDF 1.2 فصاعداً، ويرفض saIncremental لأن مراجعة ملحقة كانت ستكسر التخطيط
إخراج Fast Web View والتحميل التدريجي وواجهة التوفر كلها جزء من مكوّن PDFium لـ Delphi و C++Builder، الذي يغلّف PDFium لمشاريع Delphi و C++Builder و Lazarus