مقال تقني

دمج وتقسيم ملفات PDF بحجم جيجابايت في Delphi مع الوصول المباشر لـ PDFlibPas

دمج أو تقسيم ملف PDF بحجم 2 جيجابايت بالطريقة الواضحة يكلفك شيئين في وقت واحد: وقت الساعة ومساحة العنوان. الطريقة الواضحة هي تحميل كل إدخال، والقيام بالعمل، وكتابة الإخراج. التحميل هو المكان الذي تنكسر فيه الأمور. يضاعف أرشيف المسح الضوئي الذي ينتقل من 300 إلى 600 نقطة في البوصة (DPI) دقته الخطية ويتضاعف أربع مرات تقريبًا على القرص، لذلك فإن نفس مهمة التجميع التي تعاملت مع ملفات 400 ميجابايت طوال العام تبدأ في التخبط في اللحظة التي يتجاوز فيها الإدخال جيجابايت، غالبًا أثناء عدم القيام بأي شيء أكثر من عد الصفحات. لم تزدد المهمة صعوبة أبدًا. الفتح، والعد، واختيار النطاقات، والتسلسل هو كل ما في الأمر. ببساطة، توقف تحميل الشجرة بالكامل عن كونه إعدادًا افتراضيًا سليمًا بهذا الحجم. يجيب PDFlibPas، مكتبة PDF الخاصة بـ losLab لـ Delphi و C++Builder، على هذا بطبقة الوصول المباشر (Direct Access) الخاصة به: مجموعة من الوظائف المسبوقة بـ DA مدعومة بقارئ تدفق يمشي جدول المراجع التبادلية في مكانه بدلاً من بناء المستند بأكمله في الذاكرة

أين تذهب الذاكرة في حمولة كاملة

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

طبقة الوصول المباشر تعكس النموذج. يقوم DAOpenFile و DAOpenFileReadOnly بتحليل المقطع الختامي و xref، وهما بضعة كيلوبايتات في ذيل الملف، ويعيدان مقبض ملف. يتم جلب الكائنات بشكل كسول (lazily) عندما يحتاج إليها الاستدعاء. النتيجة العملية هي أن فتح ملف بحجم جيجابايت متعدد يستغرق وقتًا تقريبًا نفس وقت فتح ملف صغير، وتتتبع الذاكرة ما تلمسه بدلاً من ما يحتويه الملف

فحص ملف ضخم دون تحميله

يأتي النمط أدناه من معيار الملفات الكبيرة الخاص بالمكتبة: افتح للقراءة فقط، واطرح الأسئلة، وأغلق. لا توجد شجرة مستندات على الإطلاق

var
  Lib: TPDFlib;
  Handle, Pages: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Handle := Lib.DAOpenFileReadOnly('archive-2025.pdf', '');
    if Handle = 0 then
      raise Exception.Create('Direct access open failed');
    Pages := Lib.DAGetPageCount(Handle);
    Writeln('pages : ', Pages);
    Writeln('title : ', Lib.DAGetInformation(Handle, 'Title'));
    Lib.DACloseFile(Handle);
  finally
    Lib.Free;
  end;
end;

يستحق وضع القراءة فقط التفضيل كلما استطعت: فهو يتيح تشغيل مرحلة الاستيعاب بينما تحتفظ العمليات الأخرى بالملف، ويوثق القصد. تفشل مرحلة الفحص التي تستدعي وظيفة طافرة عن طريق الخطأ بسرعة بدلاً من إتلاف الأرشيف

PageRef هو مقبض كائن، وليس رقم صفحة

الخطأ الأكثر شيوعًا باستخدام واجهة برمجة تطبيقات DA هو تمرير رقم صفحة حيث تتوقع الوظيفة PageRef. تقريبًا كل استدعاء DA لكل صفحة يأخذ مقبضًا مرجعيًا لكائن الصفحة بدلاً من رقم الصفحة: DAExtractPageText، DARenderPageToFile، DARotatePage، و DACapturePage جميعها تتوقع مرجعًا (ref). يمكنك الحصول على واحد عن طريق ترجمة الرقم المواجه للإنسان من خلال DAFindPage:

PageRef := Lib.DAFindPage(Handle, 250);          // page number -> object handle
if PageRef <> 0 then
begin
  Text := Lib.DAExtractPageText(Handle, PageRef, 0);
  Lib.DARenderPageToFile(Handle, PageRef, 5, 150, 'page250.png');
end;

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

دمج مئات الملفات بقائمة مسماة

بالنسبة لملفين، يكفي MergeFiles(First, Second, Output). يتوسع تجميع الدُفعات بشكل أفضل من خلال قوائم الملفات: سجل الإدخالات تحت اسم قائمة، ثم ادمج القائمة في تمريرة واحدة

Lib.AddToFileList('Statements', 'jan.pdf');
Lib.AddToFileList('Statements', 'feb.pdf');
Lib.AddToFileList('Statements', 'mar.pdf');
Lib.MergeFileList('Statements', 'q1-statements.pdf');

// Verify the result the cheap way: direct access again
Handle := Lib.DAOpenFileReadOnly('q1-statements.pdf', '');
Writeln('merged pages: ', Lib.DAGetPageCount(Handle));
Lib.DACloseFile(Handle);

تحتوي عائلة الدمج على ثلاثة متغيرات، والاختلاف ليس في السرعة وحدها. يتخطى MergeFileListFast الحفاظ على شجرة الهيكل؛ يفرض MergeFileListStrict وضعًا صارمًا؛ النسخة غير الملحقة هي الافتراضي المتوازن. القاعدة التشغيلية التي تبرز: إذا كان أي إدخال عبارة عن ملف PDF موسوم يجب أن ينجو هيكل إمكانية الوصول الخاص به، وهو أي شيء يتم إنتاجه لـ PDF/UA كونه الحالة الواضحة، فتواصل مع المتغير الافتراضي أو الصارم، لأن وضع Fast يسقط شجرة الهيكل بصمت. بالنسبة لأرشيفات المسح الضوئي البسيطة التي لا تحتوي على علامات، فإن Fast هو أداء مجاني. قرر لكل خط أنابيب، وليس لكل مزاج مطور، وقم بتسجيل المتغير المستخدم في سجل المهام

التقسيم بدون تحميل: استخراج النطاق

التقسيم يتبع نفس فلسفة عدم التحميل. يقوم ExtractFilePages(InputFileName, Password, OutputFileName, RangeList) بسحب نطاق صفحة مباشرة من ملف إلى ملف، مع قائمة نطاق مثل '1-500'، '501-1000'، أو التحديدات المفصولة بفواصل، والمصدر لا يصبح شجرة مستندات أبدًا. عندما يكون المستند محملاً بالفعل لأسباب أخرى، يُنتج ExtractPageRanges مستندًا جديدًا في الذاكرة من المستند الحالي، ويسحب CopyPageRanges النطاقات عبر من مستند آخر محمل بواسطة المعرف. بالنسبة للتقسيم لكل كشف لتدفقات الطباعة المدمجة، فإن نموذج من ملف إلى ملف هو الذي يمنع إدخال بحجم 4 جيجابايت من التضخم إلى ذاكرة الوصول العشوائي

الملفات التي تكذب حول هندستها

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

أولاً، الرؤوس المزاحة. تقوم بوابات البريد ومجمعات الطباعة أحيانًا بإضافة بايتات إلى ملف PDF، وبالتالي لا تعود علامة %PDF موجودة في الإزاحة 0 وكل إزاحة xref في الملف تكون خاطئة بنفس المقدار. يكتشف قارئ التدفق هذا ويكشف عنه (DAShiftedHeader على المستوى المسطح، ShiftedHeader على TSmartPDFReader)، ثم يعوضه أثناء القراءات. الحساب الجبري للإزاحة المطور محليًا لا يفعل ذلك عادةً، وهذا هو السبب في أن "العمل على كل ملف ننشئه، والفشل في الملفات الواردة من العميل X" هو العرض الكلاسيكي

ثانيًا، جداول المراجع التبادلية المعطلة. يقوم DACopyFile(InputFileName, OutputFileName, PageCount) بتدفق الملف بأكمله إلى نسخة جديدة مع إعادة بناء xref، وإرجاع عدد الصفحات كمنتج ثانوي. يؤدي تشغيله كمرحلة تطبيع أمام مستهلك متطلب من المصب إلى تحويل فئة من إخفاقات التحليل المتقطع إلى خطوة إصلاح واحدة يمكن التنبؤ بها. وعندما تحتاج تعديلاتك الخاصة إلى الحفظ، يكتبها DAAppendFile كتحديث تدريجي، ويلحق مراجعة جديدة بدلاً من إعادة كتابة جيجابايت، مما يحافظ على تكلفة الحفظ متناسبة مع التغيير بدلاً من الملف

تفاصيل التسليم: الخطية والتكوين

إمكانيتان متجاورتان تكملان خط أنابيب ملف كبير. عندما يتم تقديم الإخراج المجمع عبر HTTP للعرض داخل المستعرض، يقوم LinearizeFile بإعادة تنظيمه لتدفق نطاق البايت بحيث يتم عرض الصفحة الأولى قبل انتهاء تنزيل باقي حزمة 500 ميجابايت. قم بتشغيله كمرحلة نهائية، بعد كل الدمج، لأن أي تعديل لاحق يؤدي إلى إلغاء خطية الملف مرة أخرى. وعندما تحتاج الحزم إلى تكوين بدلاً من تسلسل بسيط، لنفترض ورقة غلاف مختومة خلف كل كشف أو فرض صفحتين مصدر على ورقة إخراج واحدة، يحول DACapturePage أي صفحة إلى قالب قابل لإعادة الاستخدام يضعه DADrawCapturedPage في صفحة وجهة في مستطيل عشوائي، لا يزال بدون تحميل مستند كامل على المصدر متعدد الجيجابايت

الحدود وما يبقى للقراءة فقط

ينفد التنسيق نفسه من الغرفة قبل فترة طويلة من الوصول المباشر. الإزاحات هي Int64 طوال الطريق عبر طبقة DA، وبالتالي فإن الأسقف الحقيقية هي القرص المتاح وحقل إزاحة xref المكون من 10 أرقام لجداول المراجع التبادلية الكلاسيكية (غير التدفق). أرشيفات المسح الضوئي متعددة الجيجابايت غير ملحوظة في الممارسة العملية، وتبقى الذاكرة مقيدة بغض النظر عن حجم الملف لأنه تتم قراءة الكائنات فقط عندما يطلبها الاستدعاء

غالبًا ما يتم طرح سؤالين بما يكفي للإجابة عليهما بشكل مباشر. يحمل الدمج من خلال المسار الافتراضي هيكل المستند، لذلك تبقى الإشارات المرجعية والروابط؛ متغير Fast هو المتغير الذي يستبدل شجرة الهيكل بالسرعة، وهذا هو السبب الكامل لحجزها للإدخالات غير الموسومة. العادة الآمنة هي فتح الإخراج المدمج، والمشي في مخططه التفصيلي، والتحقق الفوري من بعض الروابط الداخلية قبل شحنها. أما بالنسبة للتحرير: هناك أرضية وسطى مفيدة بين الفحص للقراءة فقط والحمل الكامل. تعمل العمليات على مستوى الصفحة على المقبض مباشرةً، DARotatePage، DAMovePage، و DAHidePage من بينها، جنبًا إلى جنب مع قراءات حقل النموذج، ويستمر DAAppendFile في تلك التعديلات كمراجعة تزايدية. لا يزال التحرير على مستوى المحتوى، أي شيء يعيد كتابة عوامل التشغيل التعليمية داخل الصفحة، ينتمي إلى طبقة المستند بالكامل

مقالات ذات صلة

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

يتم شحن قائمة وظائف الوصول المباشر الكاملة مع المكتبة؛ الإصدارات والتنزيلات التجريبية موجودة في صفحة منتج PDFlibPas