قسّم ملف PDF واستخرج نطاقات الصفحات
لمحة سريعة
قسم بعنوان «لمحة سريعة»لديك ملف PDF واحد، وتحتاج إلى عدّة. تنحت هذه الوصفة مستندًا واحدًا إلى ملفات متعددة
عبر واجهة التقسيم في Core، وهي NextPDF\Document\PdfSplitter. تمرّر المصدر بوصفه
سلسلة بايتات PDF خامًا وتصف الصفحات التي تريدها. يحلّل المُقسِّم المصدر عبر رسم
الكائنات، وينسخ الكائنات القابلة للوصول من كل نطاق مطلوب إلى مستند جديد مُعاد ترقيمه له
شجرة صفحاته وجدول مراجعه التقاطعية الخاصان، ويعيد إليك ملفات PDF مكتملة بنيويًا تُحمَّل
في قارئ متوافق.
هذا عكس وصفة الدمج: يؤلّف الدمج مستندات كثيرة في واحد، ويفكّك التقسيم مستندًا واحدًا إلى مستندات كثيرة. وتغطّي الواجهة نفسها المهام الثلاث التي تحتاجها أكثر ما تحتاج:
- التقسيم بالنطاقات — أنتج مستند إخراج واحدًا لكل نطاق صفحات تسمّيه.
- التقسيم كل N صفحات — قطّع ملفًا طويلًا إلى أجزاء ثابتة الحجم.
- استخراج نطاق — اسحب نطاق صفحات متّصلًا واحدًا إلى مستند واحد.
يجري التقسيم داخل العملية، دون متصفّح بلا واجهة أو نداء شبكي. تحتاج إلى تثبيت Core
(composer require nextpdf/core:^3) وملف PDF واحد قابل للقراءة.
التثبيت
قسم بعنوان «التثبيت»composer require nextpdf/core:^3نظرة مفاهيمية عامة
قسم بعنوان «نظرة مفاهيمية عامة»يحدّد ملف PDF موقع صفحاته عبر شجرة صفحات جذرها عقدة /Pages، ويصل إلى كل كائن غير
مباشر عبر بيانات مراجعه التقاطعية (جدول أو مجرى). لا يمكنك استخراج الصفحات بتقطيع
البايتات: فالصفحة الواحدة تشير إلى خطوط وصور وقواميس موارد مشتركة تقيم في مكان آخر من
الملف، ولن تبقى إزاحات المراجع التقاطعية صالحة.
يؤدي PdfSplitter العمل الفعلي. فلكل نطاق يجوب رسم الكائنات انطلاقًا من كائنات الصفحات
المطلوبة، ويجمع إغلاق الكائنات القابل للوصول، ويعيد ترقيم تلك الكائنات في فضاء عناوين
جديد، ويعيد بناء مستند بشجرة صفحات واحدة، ويُصدر جدول مراجع تقاطعية حقيقيًا وفق بنية
PDF 2.0 (ISO 32000-2:2020، جدول المراجع التقاطعية §7.5.4، شجرة الصفحات §7.7.3). كل
إخراج مستند مكتفٍ بذاته، لا قطعة منه.
أرقام الصفحات تبدأ من 1 وشاملة. والنطاق كائن قيمة من NextPDF\Document\PageRange:
يعني new PageRange(2, 5) الصفحات من 2 إلى 5. يتحقق المُنشئ من ثوابته الخاصة — فهو
يرفض بداية أقل من 1 أو نهاية قبل البداية برفع NextPDF\Exception\PageLayoutException —
فيفشل النطاق المستحيل عند الإنشاء، لا في عمق المُقسِّم. ويرفع كل من PageRange::parse()
وPageRange::all() PageLayoutException نفسه عند مواصفة مشوّهة أو إجمالي صفحات غير
موجب.
واجهة API
قسم بعنوان «واجهة API»يكشف new NextPDF\Document\PdfSplitter() عن ثلاث طرائق. تأخذ جميعها المصدر بوصفه سلسلة
بايتات PDF خامًا، لا مسارًا أبدًا.
-
split(string $pdfData, array $ranges, int $maxBytes = 100_000_000, int $maxRanges = 1000): SplitResultينتج مستند إخراج واحدًا لكلPageRangeفي$ranges، بالترتيب. ويحدّ المعاملان المحدّان حجم المدخل وعدد النطاقات. -
splitEvery(string $pdfData, int $pagesPerSegment): SplitResultيقطّع المستند إلى أجزاء ثابتة الحجم بمقدار$pagesPerSegmentصفحة لكل جزء؛ ويحمل الجزء الأخير الباقي. -
extractPages(string $pdfData, PageRange $range): SplitDocumentيستخرج نطاقًا واحدًا ويعيد ذلك المستند الواحد مباشرةً.
يعيد split() وsplitEvery() كائن NextPDF\Document\SplitResult، وهو كائن readonly
يحمل $documents (قائمة بالأجزاء) و$totalPages (صفحات المصدر) و$sourceSize. ويتيح
count() وdocument(int $index) لجلب جزء بفهرسه المبدوء بالصفر، وtotalOutputSize().
كل جزء، وقيمة إرجاع extractPages()، كائن NextPDF\Document\SplitDocument: كائن
readonly يكشف عن $pdfData (بايتات الجزء) و$range و$pageCount و$sizeBytes
والمساعد isValid(). إن isValid() فحص سلامة ضيّق لترويسة %PDF — يعيد true حين
تبدأ بايتات الجزء بـ%PDF — لا تحقق من بنية المستند أو من المطابقة؛ فهو يؤكد أن المُقسِّم
أنتج ملف PDF، لا أن الملف مطابق تمامًا.
تبني PageRange مباشرةً بـnew PageRange($start, $end)، أو تحلّل مواصفة مقروءة
للبشر بـPageRange::parse('1-3,5,7-10') التي تعيد list<PageRange> جاهزة لتمريرها إلى
split(). ويعيد PageRange::all($totalPages) نطاقًا واحدًا يغطّي المستند كله.
نموذج شيفرة — بداية سريعة
قسم بعنوان «نموذج شيفرة — بداية سريعة»يقرأ هذا النموذج ملفًا واحدًا ويقسّمه إلى مستندين: الصفحات من 1 إلى 3، والصفحات من 4 إلى 6. ويترك معالجة الأخطاء جانبًا ليُظهر شكل الاستدعاء؛ ويضيف نموذج الإنتاج أدناه كامل الحراسات.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Document\PageRange;use NextPDF\Document\PdfSplitter;
$splitter = new PdfSplitter();
$result = $splitter->split( file_get_contents(__DIR__ . '/report.pdf'), [ new PageRange(1, 3), new PageRange(4, 6), ],);
foreach ($result->documents as $i => $segment) { file_put_contents(__DIR__ . sprintf('/part-%d.pdf', $i + 1), $segment->pdfData);}
printf("Split %d-page source into %d document(s).\n", $result->totalPages, $result->count());نموذج شيفرة — الإنتاج
قسم بعنوان «نموذج شيفرة — الإنتاج»يبني هذا البرنامج المكتفي بذاته مستندًا صغيرًا متعدّد الصفحات في الذاكرة، فيعمل دون ملف
خارجي. وهو يبرهن على العمليات الثلاث جميعها — التقسيم بالنطاقات، والتقسيم كل N صفحات،
واستخراج نطاق واحد. يتحقق من أجزاء النطاقات والذيل المستخرج ويكتبها، ويبلّغ عن نتيجة
التقسيم بالحجم بصورة عدد، فترى شكل كل استدعاء دون ثلاث حلقات كتابة شبه متطابقة. يلتقط
الاستثناءات التي ترفعها واجهة التقسيم ويعيد رفع كلٍّ منها بسياق بدلًا من ابتلاعها. استبدل
المصدر في الذاكرة بقراءتك أنت عبر file_get_contents() أو بجلب من مخزن كائنات.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use InvalidArgumentException;use NextPDF\Core\Document;use NextPDF\Document\Merge\UnsupportedSourceDocumentException;use NextPDF\Document\PageRange;use NextPDF\Document\PdfSplitter;use NextPDF\Document\SplitDocument;use NextPDF\Exception\PageLayoutException;
/** * Build a tiny labelled multi-page PDF so the program is self-contained. * * In your own code, replace this with a read of the PDF you want to split, * for example file_get_contents($path). */function buildSample(int $pages): string{ $doc = Document::createStandalone(); $doc->setTitle('Split sample');
for ($page = 1; $page <= $pages; $page++) { $doc->addPage(); $doc->setFont('helvetica', '', 12); $doc->cell(0, 10, sprintf('Source page %d', $page), newLine: true); }
return $doc->getPdfData();}
$source = buildSample(7);
$splitter = new PdfSplitter();
try { // 1. Split into named ranges: one output per PageRange, in order. $byRange = $splitter->split( $source, PageRange::parse('1-3,4-6'), maxBytes: 50_000_000, maxRanges: 100, );
// 2. Split every 2 pages: segments of [1-2], [3-4], [5-6], [7] (remainder). $bySize = $splitter->splitEvery($source, 2);
// 3. Extract a single range as one document. $tail = $splitter->extractPages($source, new PageRange(7, 7));} catch (InvalidArgumentException $e) { // Raised on an oversized input, an empty range list, or too many ranges. throw new RuntimeException('Split rejected its input: ' . $e->getMessage(), previous: $e);} catch (PageLayoutException $e) { // Raised when a range exceeds the source page count, and also by the // PageRange constructor / PageRange::parse() on an invalid or malformed range. throw new RuntimeException( sprintf('Range out of bounds (page %d): %s', $e->getPageNumber(), $e->getConstraint()), previous: $e, );} catch (UnsupportedSourceDocumentException $e) { // Raised fail-closed on an encrypted, signed, or form-bearing source. throw new RuntimeException('Source cannot be split: ' . $e->getMessage(), previous: $e);}
printf( "Source has %d page(s). By-range produced %d doc(s); by-size produced %d doc(s).\n", $byRange->totalPages, $byRange->count(), $bySize->count(),);
foreach ($byRange->documents as $i => $segment) { emitSegment(sprintf('range-%d', $i + 1), $segment);}
emitSegment('tail', $tail);
/** * Validate a segment and write it to the cookbook side-channel directory, * or to the script directory by default. */function emitSegment(string $name, SplitDocument $segment): void{ if (!$segment->isValid()) { throw new RuntimeException(sprintf('Segment "%s" failed its %%PDF header check.', $name)); }
$dir = getenv('NEXTPDF_COOKBOOK_OUTPUT'); $dir = $dir !== false && $dir !== '' ? $dir : __DIR__; $path = sprintf('%s/%s.pdf', rtrim($dir, '/'), $name);
if (file_put_contents($path, $segment->pdfData) === false) { throw new RuntimeException(sprintf('Could not write segment to "%s".', $path)); }
printf("Wrote %s: pages %d-%d, %d bytes.\n", $name, $segment->range->start, $segment->range->end, $segment->sizeBytes);}الخرج القياسي المتوقع (تعتمد أحجام البايتات على البناء):
Source has 7 page(s). By-range produced 2 doc(s); by-size produced 4 doc(s).Wrote range-1: pages 1-3, <n> bytes.Wrote range-2: pages 4-6, <n> bytes.Wrote tail: pages 7-7, <n> bytes.الحالات الحدّية والمزالق
قسم بعنوان «الحالات الحدّية والمزالق»- المصدر بايتات، لا مسار. كل طريقة تأخذ سلسلة PDF خامًا. اقرأ الملف بـ
file_get_contents()أولًا، أو اسحب البايتات من مخزن كائنات. يجعل تمرير مسار المصدر يفشل في التحليل. - أرقام الصفحات تبدأ من 1 وشاملة. يغطّي
new PageRange(1, 3)الصفحات 1 و2 و3 — ثلاث صفحات. وترفع بداية أقل من 1 أو نهاية قبل البدايةPageLayoutExceptionمن مُنشئPageRangeنفسه. - النطاق بعد النهاية خطأ، لا تقليم. إذا تجاوزت نهاية نطاق عدد صفحات المصدر، رفع
split()PageLayoutException؛ ولا يقصّ النطاق بصمت إلى آخر صفحة أبدًا. افحص عدد الصفحات أولًا إذا كانت نطاقاتك مزوّدة من المستدعي. - يحتفظ
splitEvery()بالباقي. يحمل الجزء الأخير ما تبقّى من صفحات، فمستند من 7 صفحات مقسّم كل صفحتين يعطي أربعة أجزاء: ثلاثة من صفحتين وواحد من صفحة. يجب أن يكون$pagesPerSegment1 على الأقل، وإلا حصلت علىInvalidArgumentException. - قائمة النطاقات الفارغة مرفوضة. يرفع
split()مع$ranges === []InvalidArgumentException. ابنِ نطاقًا واحدًا على الأقل قبل استدعائه. - الحدود ترفع بدلًا من البتر. يرفع تجاوز
maxBytesأوmaxRangesInvalidArgumentException. ولا يعالج المُقسِّم مدخلًا مفرطًا جزئيًا أبدًا، فاضبط كلا الحدّين لحملك. - المصادر المشفّرة والموقّعة والحاملة لنموذج تفشل بإغلاق. يرفع مصدر مشفّر (لا
يمكن نسخه دون المفتاح)، أو مصدر موقّع رقميًا (تعيد الترقيم تُبطل نطاق بايتات
التوقيع)، أو مصدر يحمل نموذجًا تفاعليًا (قد تقع أدوات حقل على صفحات مُسقَطة فتتيتّم)
UnsupportedSourceDocumentException. يرفض المُقسِّم بدلًا من إصدار مستند تالف أو مُخترَق. وتقسيم مستند نموذج قيد معروف في هذا الإصدار. -
UnsupportedSourceDocumentExceptionتقيم تحت فضاء الأسماء Merge. اسمها المؤهّل بالكاملNextPDF\Document\Merge\UnsupportedSourceDocumentException. ذلك المسارMergeفي صفحة تقسيم ليس خطأ نسخ ولصق: إنه استثناء رفض مستند المصدر الوحيد المشترك الذي ترفعه واجهتا الدمج والتقسيم كلتاهما حين لا يمكن نسخ مصدر بأمان. استورده من ذلك الفضاء. - الإخراج بنيويًا جديد، لا مستقرّ البايتات. كل جزء مستند جديد له فهرسه وشجرة صفحاته
ومقطورته الخاصة. تشغيلان على المدخل نفسه متساويان بنيويًا، لكن غير مضمونَي تطابق
البايتات — ومن هنا ملف تعريف إعادة الإنتاج
structural.
الأداء
قسم بعنوان «الأداء»التقسيم خطّي في عدد الصفحات المنسوخة عبر كل النطاقات. ويهيمن على العمل تحليل المصدر
ونسخ إغلاق كائنات كل نطاق، لا مسك المُقسِّم لدفاتره. يُحتفظ بالمصدر في الذاكرة بوصفه
سلسلة، وتُحتفظ بايتات كل جزء حتى تكتبها، فيتتبع الذروة في الذاكرة حجم المصدر زائدًا أكبر
نطاق تنتجه. وتُبقي حارسة maxBytes جانب المصدر من تلك الذروة محدودًا. ولخطوط الأنابيب
عالية الحجم، اضبط maxBytes وmaxRanges على أصغر قيم يحتاجها حملك، فيفشل مدخل مشوّه
أو مفرط سريعًا بدلًا من استنزاف الذاكرة.
ملاحظات أمنية
قسم بعنوان «ملاحظات أمنية»يجري التقسيم داخل العملية؛ فلا تغادر بايتات أي مستند المضيف، ولا يُجرى نداء شبكي. عامِل كل ملف PDF مصدر بوصفه مدخلًا غير موثوق:
- أبقِ الحدود ضيّقة.
maxBytesوmaxRangesخط دفاعك الأول ضد مدخل حجب الخدمة. لأي واجهة تقبل تحميلات، اضبطهما على سقفك الحقيقي، لا على القيم الافتراضية السخيّة. - افرز قبل أن تقسّم. يفشل بإغلاق مصدر مشفّر أو موقّع، لكن يمكنك كشف تلك الحالات أبكر. مرّر المدخلات غير الموثوقة عبر مفتّش Core أولًا. انظر تحليل ملف PDF وفحصه لمسح محدود يعلّم التشفير والتوقيعات وعلامات الخطر قبل معالجة أثقل.
- لا تُقحِم مدخل المستخدم في مسار أبدًا. تكتب هذه الوصفة إلى دليل ثابت أو إلى قناة الكتاب الجانبية. اشتقّ مسارات الإخراج وأسماء الأجزاء من قيم يتحكّم بها الخادم، لا من حقل طلب، لتفادي اجتياز المسارات.
- لا أسرار في الإخراج. لا تكتب ملفات الأجزاء إلى موقع، أو باسم، يكشف معرّفات داخلية لعميل لا ينبغي أن يراها.
المطابقة
قسم بعنوان «المطابقة»لا تدّعي هذه الوصفة أي ادعاء معياري خاص بها. فهي تفكّك مستندًا واحدًا عبر واجهة التقسيم
في Core وتفحص سلامة كل جزء بفحص ترويسة %PDF في SplitDocument::isValid() — فحص وجود
بأن المُقسِّم أصدر ملف PDF، لا تحقق من المطابقة أو من بنية المستند. وبنى شجرة الصفحات
والمراجع التقاطعية التي يعيد PdfSplitter بناءها لكل جزء هي بنى PDF 2.0 الموصوفة في
مرجع /modules/core/document/ (ISO 32000-2:2020، جدول المراجع التقاطعية §7.5.4،
شجرة الصفحات §7.7.3). لقراءة بنيوية لأي مستند مدخل أو إخراج، تشمل الإصدار وعدد الصفحات
والتشفير وأعلام التوقيع، استخدم مفتّش Core الموثّق في
تحليل ملف PDF وفحصه.
انظر أيضًا
قسم بعنوان «انظر أيضًا»- مرجع وحدة المستند — واجهة التقسيم والدمج وأجزاء المستند الكاملة.
- دمج ملفات PDF خارجية — الوصفة العكسية: تأليف مستندات كثيرة في واحد.
- تحليل ملف PDF وفحصه — افرز المدخلات غير الموثوقة قبل أن تقسّمها.
- معالجة الأخطاء الواعية بالاستثناءات
— هرمية استثناءات NextPDF خلف
PageLayoutExceptionوUnsupportedSourceDocumentException.