المستند: أجزاء DPart، التقسيم / الدمج، وامتدادات المورِّد
لمحة سريعة
قسم بعنوان «لمحة سريعة»تتعامل وحدة Document مع ملفات Portable Document Format (PDF) كاملة، وليس مع محتوى الصفحات. فهي تبني التسلسل الهرمي لأجزاء المستند الذي تستخدمه سير العمل الخاضعة للتنظيم لإرفاق البيانات الوصفية، وتقسِّم ملف PDF إلى مقاطع حسب نطاقات الصفحات، وتدمج عدة ملفات PDF في ملف واحد، وتسجِّل امتدادات المطوِّرين في فهرس المستند.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3نظرة عامة مفاهيمية
قسم بعنوان «نظرة عامة مفاهيمية»تعمل هذه الوحدة فوق مستوى محتوى الصفحات. فبينما تُصدر وحدتا Graphics وContent المعاملات، تعمل وحدة Document على المستوى البنيوي: أشجار الصفحات، وفهرس المستند، وشجرة أجزاء المستند.
جزء المستند (DPart) هو تقسيم منطقي لملف PDF. تحدِّد ISO 32000-2 تسلسلاً هرمياً لأجزاء DPart تحمل عقده بيانات أجزاء المستند الوصفية (DPM). ويمكن لسير عمل خاضع للتنظيم، كسير عمل صيدلاني أو قانوني أو أرشيفي، أن يربط البيانات الوصفية بنطاق فرعي من الصفحات بدلاً من ربطها بالملف كاملاً — §14.12. DPart عقدة ثابتة من نوع readonly: تشير العقدة الطرفية إلى تسلسل متجاور من فهارس الصفحات، وتجمع العقدة الوسيطة عقد DPart الأبناء في شجرة. DPartRoot هو جذر الشجرة الذي يسلسله الكاتب Writer. ومدخلا /Start و/End في العقدة الطرفية مرجعان غير مباشرين إلى كائنات الصفحات، وليسا عددين صحيحين لفهرس الصفحة — §14.12. يحلّ DPart::resolveWithPageObjects() تلك المدخلات مقابل خريطة فهرس الصفحة→رقم الكائن التي يوفِّرها الكاتب، ويُعيد صيغة مرجع /Start (و/End الاختياري). ولا يلجأ إلى صيغة العدد الصحيح إلا في مسارات الاختبار عندما لا تكون الخريطة متوفرة.
PdfMerger وPdfSplitter هما واجهة تأليف المستندات. يدمج PdfMerger كائنات الصفحات من عدة ملفات PDF مدخلة، ويعيد ترقيم الكائنات لتجنب التعارضات، ثم يعيد بناء شجرة صفحات واحدة وجدول مرجعية متقاطعة واحد. شجرة الصفحات التي ينتجها هي عقدة Pages متوازنة تحتوي Kids وCount، إضافةً إلى نموذج السمات القابلة للوراثة الذي يحدِّده PDF لعقد شجرة الصفحات — §7.7.3. أما PdfSplitter فينفِّذ العملية العكسية: إذ يستخرج نطاقات الصفحات في كائنات SplitDocument مستقلة. PageRange هو كائن القيمة الذي يستخدمه كلا الصنفين. يبدأ ترقيمه من 1، ويتحقق من حدوده، ويجيب عن contains() وcount() وtoArray().
تُنمذِج VendorExtensionRegistry وExtensionsDictionary وDeveloperExtensionEntry قاموس امتدادات المطوِّرين في فهرس المستند. يستخدم المحرك ذلك القاموس للإعلان عن مستوى امتداد للمورِّد يتجاوز المواصفة الأساسية. ويرفض السجل عمليات إعادة التسجيل المتعارضة لنفس بادئة المورِّد عبر VendorExtensionRegistryConflictException. كما تُنمذِج CollectionDictionary وCollectionSort مدخل فهرس مجموعة PDF (مجموعة محمولة أو حافظة).
واجهة API
قسم بعنوان «واجهة API»| الصنف | الطرائق الرئيسية | الدور |
|---|---|---|
DPart | isLeaf(), hasMetadata(), resolveWithPageObjects(), write() | عقدة جزء مستند ثابتة (@since 1.12.0) |
DPartRoot | isEmpty(), write() | جذر شجرة DPart الذي يسلسله الكاتب Writer (@since 1.12.0) |
PdfMerger | merge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append() | دمج عدة ملفات PDF مع إعادة ترقيم الكائنات (@since 1.9.0) |
PdfSplitter | split(), splitEvery(), extractPages() | تقسيم بحسب نطاق الصفحات إلى SplitDocument (@since 1.9.0) |
PageRange | contains(int $page), count(), toArray() | كائن قيمة لنطاق الصفحات يبدأ ترقيمه من 1 |
MergeResult / SplitResult | isValid(), count(), document(), totalOutputSize() | كائنات نتيجة التأليف |
VendorExtensionRegistry | تسجيل الامتدادات | سجل امتدادات المطوِّرين (@since 2.2.0) |
ExtensionsDictionary | withEntry(), entries(), isEmpty(), toPdfDictionary() | باني قاموس امتدادات ثابت (@since 2.0.0) |
CollectionDictionary | toPdfDictionary() | مدخل فهرس لمجموعة محمولة (@since 2.0.0) |
شغِّل composer docs:generate-api-php -- --module=Document لتوليد جدول PHPDoc الكامل.
مثال برمجي — بداية سريعة
قسم بعنوان «مثال برمجي — بداية سريعة»قسِّم ملف PDF إلى مستندات من صفحة واحدة، ثم افحص النتيجة.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Document\PageRange;use NextPDF\Document\PdfSplitter;
$splitter = new PdfSplitter();$result = $splitter->splitEvery(file_get_contents('/srv/in/report.pdf'), 1);
foreach (range(0, $result->count() - 1) as $index) { $segment = $result->document($index); file_put_contents("/srv/out/page-{$index}.pdf", $segment->pdfData);}
$singlePage = $splitter->extractPages( file_get_contents('/srv/in/report.pdf'), new PageRange(2, 4),);مثال برمجي — بيئة الإنتاج
قسم بعنوان «مثال برمجي — بيئة الإنتاج»ادمج عدة ملفات PDF ضمن ميزانية إدخال صريحة، ثم تحقَّق من صحة النتيجة قبل كتابة المخرَج المجمَّع.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Document\PdfMerger;use NextPDF\Exception\PageLayoutException;
/** @var list<string> $sources Raw PDF byte strings to combine. */$sources = array_map( static fn (string $path): string => file_get_contents($path), glob('/srv/batch/*.pdf') ?: [],);
$merger = new PdfMerger();
try { // Bound the merge: at most 50 files, 100 MB total. $merged = $merger->merge($sources, maxFiles: 50, maxTotalBytes: 100_000_000);} catch (PageLayoutException $e) { throw new \RuntimeException('Merge rejected: empty or invalid input set.', previous: $e);}
if (!$merged->isValid()) { throw new \RuntimeException('Merged document failed structural validation.');}
file_put_contents('/srv/out/combined.pdf', $merged->pdfData);الحالات الطرفية والمزالق
قسم بعنوان «الحالات الطرفية والمزالق»- يفرض
PdfMerger::merge()وPdfSplitter::split()حدود الإدخال عبرResourceGuard. فالمدخلات التي تحتوي ملفات أو بايتات أكثر من اللازم تثير استثناءً بدلاً من اقتطاعها بصمت. اضبطmaxFiles/maxTotalBytesعمداً بما يناسب حملك التشغيلي. - قائمة ملفات فارغة أو قائمة نطاقات فارغة تثير
PageLayoutException. عامِل هذه الحالات كأخطاء في التهيئة، لا كنتائج فارغة. - يبدأ ترقيم
PageRangeمن 1 وهو شامل للحدين. أما قائمةpagesفي عقدةDPartالطرفية فهي فهارس صفحات يبدأ ترقيمها من 0. يستخدم التجريدان أساسَي ترقيم مختلفين للفهارس. حوِّل صراحةً عند الانتقال بينهما. DPartمن نوعreadonly. لبناء شجرة مختلفة، أنشئ عقداً جديدة بدلاً من تعديل عقدة قائمة. لا يُعيدresolveWithPageObjects()صيغة الاحتياط القائمة على فهرس العدد الصحيح إلا عندما تكون خريطة كائنات الصفحات فارغة. لا تعتمد على ذلك المسار في مخرَج بيئة الإنتاج.- يثير
VendorExtensionRegistryالاستثناءVendorExtensionRegistryConflictExceptionعند تكرار بادئة المورِّد. سجِّل كل بادئة مرة واحدة فقط.
الأداء
قسم بعنوان «الأداء»يتوسع التقسيم والدمج خطياً مع عدد الصفحات، وتتحكم فيهما عملية التحليل وإعادة ترقيم الكائنات، لا عمليات التخزين المؤقت الداخلية للوحدة نفسها. يندرج الحمل المرجعي الافتراضي ضمن ميزانية قدرها 1500 ms زمن جداري / 64 MB ذروة. عمليات الدمج الكبيرة مقيَّدة أساساً بإجمالي بايتات الإدخال. يُبقي حارس maxTotalBytes ذروة الذاكرة محدودة. ملف تعريف قابلية إعادة الإنتاج هو structural: يحمل ملف PDF المدموج أو المقسَّم مذيِّلاً جديداً و/ID جديداً، فيكون تشغيلان متساويين بنيوياً لكن غير متطابقين على مستوى البايت.
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»يستهلك PdfMerger::merge() وPdfSplitter::split() بايتات PDF غير موثوقة. قبل التحليل، يمرِّر كلاهما الإدخال عبر ResourceGuard::assertSize() / assertCount()، مما يحدّ من هجوم حجب الخدمة الناتج عن تضخيم فك الضغط أو تضخيم عدد الكائنات. أبقِ وسائط maxFiles وmaxTotalBytes وmaxBytes ضيقة بما يناسب النشر بدلاً من الاعتماد على القيم الافتراضية. عامِل كل ملف PDF مُدخَل على أنه معادٍ. عندما تكون المصادر مقدَّمة من المستخدم، شغِّل التأليف الدُّفعي في عامل مقيَّد. راجع نموذج تهديد المحرك في /modules/core/security/ لمعرفة حدود الثقة.
المطابقة
قسم بعنوان «المطابقة»تتبع شجرة DPart التي تبنيها هذه الوحدة نموذج جزء المستند في ISO 32000-2 §14.12، مع إصدار مدخلَي /Start و/End الطرفيين كمراجع غير مباشرة إلى كائنات الصفحات بموجب البند نفسه. يستخدم المخرَج المدموج بنية عقدة شجرة الصفحات المحدَّدة في §7.7.3. هذه حقائق تنفيذية ينتجها src/Document/ وتختبرها tests/Unit/Document/ (DPartTest، DPartRootTest، DPartPageRefTest، DocumentPdfMergerDeepTest، DocumentPageRangeParseDeepTest). وهي ليست إقراراً بالمطابقة الكاملة لـ PDF 2.0 من طرف إلى طرف. تُتحقَّق مطابقة المستند الكامل بواسطة مجموعتي الأوراكل والملفات الذهبية الموصوفتين في /modules/core/conformance/.
انظر أيضاً
قسم بعنوان «انظر أيضاً»- وحدة Core
- وحدة Writer — تسلسل شجرة DPart وشجرة الصفحات.
- وحدة Metadata — Extensible Metadata Platform (XMP) المقترنة بـ DPM.
- وحدة Navigation
- نظرة عامة على المطابقة
- نموذج أمان المحرك