ข้ามไปยังเนื้อหา

Document: DPart การแยก / รวม และส่วนขยายของผู้จำหน่าย

โมดูล Document ทำงานกับไฟล์ Portable Document Format (PDF) ทั้งไฟล์ ไม่ใช่เนื้อหาระดับหน้า โมดูลนี้สร้างลำดับชั้น Document Part ที่เวิร์กโฟลว์ภายใต้การกำกับดูแลใช้แนบเมทาดาทา แยก PDF เป็นเซกเมนต์ตามช่วงหน้า รวม PDF หลายไฟล์เป็นไฟล์เดียว และลงทะเบียนส่วนขยายสำหรับนักพัฒนาในแคตาล็อกเอกสาร

Terminal window
composer require nextpdf/core:^3

โมดูลนี้อยู่เหนือชั้นเนื้อหาระดับหน้า ขณะที่ Graphics และ Content ปล่อยตัวดำเนินการออกมา Document จะทำงานในระดับโครงสร้าง ได้แก่ แผนผังหน้า แคตาล็อกเอกสาร และแผนผัง Document Part

คำว่า Document Part (DPart) หมายถึงพาร์ทิชันเชิงตรรกะของ PDF ISO 32000-2 กำหนดลำดับชั้น DPart ซึ่งโหนดต่าง ๆ เก็บ Document Part Metadata (DPM) ไว้ เวิร์กโฟลว์ภายใต้การกำกับดูแล เช่น งานเภสัชกรรม งานกฎหมาย หรืองานจัดเก็บถาวร จึงเชื่อมโยงเมทาดาทากับช่วงย่อยของหน้าได้ แทนที่จะผูกกับทั้งไฟล์ — §14.12 DPart เป็นโหนดแบบ readonly ที่เปลี่ยนค่าไม่ได้ โหนดใบอ้างอิงชุดดัชนีหน้าที่ต่อเนื่องกัน และโหนดระดับกลางจัดกลุ่มโหนดลูก DPart เป็นแผนผัง DPartRoot คือรากของแผนผังที่ Writer ซีเรียลไลซ์ รายการ /Start และ /End ของโหนดใบเป็นการอ้างอิงทางอ้อมไปยังอ็อบเจกต์หน้า ไม่ใช่จำนวนเต็มดัชนีหน้า — §14.12 DPart::resolveWithPageObjects() รีโซลฟ์รายการเหล่านี้เทียบกับแมปดัชนีหน้า→หมายเลขอ็อบเจกต์ที่ writer จัดเตรียมให้ และคืนค่ารูปแบบการอ้างอิง /Start (รวมถึง /End หากมี) เมธอดนี้จะถอยไปใช้รูปแบบจำนวนเต็มเฉพาะในเส้นทางทดสอบที่ไม่มีแมปให้ใช้งานเท่านั้น

PdfMerger และ PdfSplitter เป็นส่วนต่อประสานสำหรับประกอบเอกสาร PdfMerger รวมอ็อบเจกต์หน้าจาก PDF อินพุตหลายไฟล์ กำหนดหมายเลขอ็อบเจกต์ใหม่เพื่อหลีกเลี่ยงการชนกัน และสร้างแผนผังหน้ากับตารางอ้างอิงไขว้ขึ้นใหม่เป็นชุดเดียว แผนผังหน้าที่สร้างขึ้นเป็นโหนด Pages แบบสมดุล พร้อม Kids และ Count รวมถึงโมเดลแอตทริบิวต์ที่สืบทอดได้ซึ่ง PDF กำหนดไว้สำหรับโหนดแผนผังหน้า — §7.7.3 PdfSplitter ทำงานในทิศทางตรงกันข้าม โดยแยกช่วงหน้าเป็นอ็อบเจกต์ SplitDocument ที่เป็นอิสระ PageRange คือ value object ที่ทั้งสองคลาสใช้ โดยเริ่มนับจาก 1 ตรวจสอบความถูกต้องของขอบเขต และตอบสนองต่อ contains(), count() และ toArray() ได้

VendorExtensionRegistry, ExtensionsDictionary และ DeveloperExtensionEntry เป็นโมเดลของดิกชันนารีส่วนขยายสำหรับนักพัฒนาในแคตาล็อกเอกสาร เอนจินใช้ดิกชันนารีนี้เพื่อประกาศระดับส่วนขยายของผู้จำหน่ายที่อยู่นอกเหนือข้อกำหนดพื้นฐาน รีจิสทรีจะปฏิเสธการลงทะเบียนซ้ำของพรีฟิกซ์ผู้จำหน่ายเดียวกันที่ขัดแย้งกันด้วย VendorExtensionRegistryConflictException CollectionDictionary และ CollectionSort เป็นโมเดลของรายการแคตาล็อก collection ของ PDF (portable collection หรือ portfolio)

คลาสเมธอดสำคัญบทบาท
DPartisLeaf(), hasMetadata(), resolveWithPageObjects(), write()โหนด Document Part ที่เปลี่ยนแปลงค่าไม่ได้ (@since 1.12.0)
DPartRootisEmpty(), write()รากของแผนผัง DPart ที่ Writer ทำการซีเรียลไลซ์ (@since 1.12.0)
PdfMergermerge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append()การรวม PDF หลายไฟล์พร้อมกำหนดหมายเลขอ็อบเจกต์ใหม่ (@since 1.9.0)
PdfSplittersplit(), splitEvery(), extractPages()การแยกตามช่วงหน้าเป็น SplitDocument (@since 1.9.0)
PageRangecontains(int $page), count(), toArray()value object สำหรับช่วงหน้าที่เริ่มนับจาก 1
MergeResult / SplitResultisValid(), count(), document(), totalOutputSize()อ็อบเจกต์ผลลัพธ์ของการประกอบเอกสาร
VendorExtensionRegistryการลงทะเบียนส่วนขยายรีจิสทรีส่วนขยายสำหรับนักพัฒนา (@since 2.2.0)
ExtensionsDictionarywithEntry(), entries(), isEmpty(), toPdfDictionary()ตัวสร้างดิกชันนารีส่วนขยายที่เปลี่ยนแปลงค่าไม่ได้ (@since 2.0.0)
CollectionDictionarytoPdfDictionary()รายการแคตาล็อกของ portable-collection (@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 และนับรวมปลายช่วง โหนดใบ DPart มีรายการ pages เป็นดัชนีหน้าที่เริ่มนับจาก 0 นามธรรมทั้งสองใช้ฐานดัชนีต่างกัน ให้แปลงค่าอย่างชัดเจนเมื่อข้ามระหว่างนามธรรมทั้งสอง
  • DPart เป็น readonly หากต้องการสร้างแผนผังรูปแบบอื่น ให้สร้างโหนดใหม่แทนการเปลี่ยนแปลงโหนดที่มีอยู่ resolveWithPageObjects() จะคืนค่ารูปแบบสำรองที่เป็นดัชนีจำนวนเต็มเฉพาะเมื่อแมปอ็อบเจกต์หน้าว่างเปล่าเท่านั้น อย่าพึ่งพาเส้นทางดังกล่าวในเอาต์พุตระดับโปรดักชัน
  • VendorExtensionRegistry จะทำให้เกิด VendorExtensionRegistryConflictException สำหรับพรีฟิกซ์ผู้จำหน่ายที่ซ้ำกัน ลงทะเบียนแต่ละพรีฟิกซ์เพียงครั้งเดียว

การแยกและการรวมขยายขนาดเชิงเส้นตามจำนวนหน้า โดยภาระหลักอยู่ที่การแจงและการกำหนดหมายเลขอ็อบเจกต์ใหม่ ไม่ใช่การจัดการบันทึกภายในของโมดูลเอง เวิร์กโหลดอ้างอิงเริ่มต้นอยู่ภายในงบประมาณเวลา wall-clock 1500 ms / หน่วยความจำสูงสุด 64 MB การรวมขนาดใหญ่ถูกจำกัดเป็นหลักด้วยจำนวนไบต์อินพุตทั้งหมด ขอบเขต maxTotalBytes ช่วยควบคุมหน่วยความจำสูงสุดให้อยู่ในขอบเขต โปรไฟล์ความสามารถในการทำซ้ำเป็นแบบ structural โดย PDF ที่ผ่านการรวมหรือแยกจะมี trailer และ /ID ที่สร้างขึ้นใหม่ ดังนั้นการรันสองครั้งจึงเท่ากันในเชิงโครงสร้างแต่ไม่เหมือนกันในระดับไบต์

PdfMerger::merge() และ PdfSplitter::split() รับไบต์ PDF ที่ไม่น่าเชื่อถือเข้ามาประมวลผล ก่อนการแจง ทั้งสองจะส่งอินพุตผ่าน ResourceGuard::assertSize() / assertCount() เพื่อจำกัดการโจมตีปฏิเสธการให้บริการที่ขยายผลจากการคลายบีบอัดหรือจำนวนอ็อบเจกต์ ให้กำหนดอาร์กิวเมนต์ maxFiles, maxTotalBytes และ maxBytes ให้รัดกุมตามสภาพแวดล้อมการดีพลอย แทนการพึ่งพาค่าเริ่มต้น ให้ถือว่า PDF อินพุตทุกไฟล์เป็นภัยคุกคาม เมื่อแหล่งข้อมูลมาจากผู้ใช้ ให้รันการประกอบเอกสารแบบแบตช์ในเวิร์กเกอร์ที่ถูกจำกัดทรัพยากร ดูแบบจำลองภัยคุกคามของเอนจินใน /modules/core/security/ สำหรับขอบเขตความเชื่อถือ

แผนผัง DPart ที่โมดูลนี้สร้างขึ้นเป็นไปตามแบบจำลอง Document Part ใน ISO 32000-2 §14.12 โดยปล่อยรายการ /Start และ /End ของโหนดใบเป็นการอ้างอิงทางอ้อมไปยังอ็อบเจกต์หน้าภายใต้ข้อกำหนดเดียวกัน เอาต์พุตที่รวมแล้วใช้โครงสร้างโหนดแผนผังหน้าตามที่กำหนดไว้ใน §7.7.3 ข้อความเหล่านี้เป็นข้อเท็จจริงของการนำไปใช้ที่สร้างโดย src/Document/ และทดสอบโดย tests/Unit/Document/ (DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest) ไม่ใช่คำยืนยันว่า PDF 2.0 สอดคล้องครบวงจร ความสอดคล้องของเอกสารทั้งฉบับได้รับการตรวจสอบโดยชุดทดสอบ oracle และ golden ที่อธิบายไว้ใน /modules/core/conformance/ แล้ว