Przejdź do głównej zawartości

Document: DPart, podział i scalanie oraz rozszerzenia producenta

Moduł Document działa na całych plikach w formacie Portable Document Format (PDF), a nie na treści stron. Buduje hierarchię części dokumentu używaną w regulowanych procesach do dołączania metadanych. Dzieli plik PDF na segmenty według zakresów stron, scala kilka plików PDF w jeden i rejestruje rozszerzenia deweloperskie w katalogu dokumentu.

Okno terminala
composer require nextpdf/core:^3

Ten moduł działa powyżej warstwy treści stron. Tam, gdzie moduły Graphics i Content emitują operatory, moduł Document działa na poziomie strukturalnym: drzewa stron, katalogu dokumentu oraz drzewa części dokumentu.

Część dokumentu (DPart) to logiczna partycja pliku PDF. ISO 32000-2 definiuje hierarchię DPart, której węzły przechowują metadane części dokumentu (DPM). Regulowany proces, na przykład farmaceutyczny, prawny lub archiwalny, może powiązać metadane z podzakresem stron zamiast z całym plikiem — §14.12. DPart to niezmienny węzeł readonly: węzeł-liść odwołuje się do ciągłego zakresu indeksów stron, a węzeł pośredni grupuje podrzędne węzły DPart w drzewo. DPartRoot to korzeń drzewa serializowany przez moduł Writer. Wpisy /Start i /End węzła-liścia są pośrednimi odwołaniami do obiektów stron, a nie całkowitoliczbowymi indeksami stron — §14.12. DPart::resolveWithPageObjects() rozwiązuje te wpisy na podstawie dostarczonej przez Writer mapy indeks strony→numer obiektu i zwraca postać odwołania /Start (oraz opcjonalnie /End). Postać całkowitoliczbową zwraca tylko na ścieżkach testowych, gdzie mapa jest niedostępna.

PdfMerger i PdfSplitter tworzą powierzchnię kompozycji dokumentu. PdfMerger łączy obiekty stron z wielu wejściowych plików PDF, ponownie numeruje obiekty, aby uniknąć kolizji, oraz odbudowuje pojedyncze drzewo stron i tablicę odsyłaczy. Tworzone przez niego drzewo stron jest zrównoważonym węzłem Pages z Kids i Count oraz modelem atrybutów dziedziczonych, który PDF definiuje dla węzłów drzewa stron — §7.7.3. PdfSplitter wykonuje operację odwrotną: wyodrębnia zakresy stron do samodzielnych obiektów SplitDocument. PageRange to obiekt wartości używany przez obie klasy. Jest indeksowany od 1, weryfikuje swoje granice oraz odpowiada na contains(), count() i toArray().

VendorExtensionRegistry, ExtensionsDictionary i DeveloperExtensionEntry modelują słownik rozszerzeń deweloperskich w katalogu dokumentu. Silnik używa tego słownika do zadeklarowania poziomu rozszerzenia producenta wykraczającego poza specyfikację bazową. Rejestr odrzuca sprzeczną ponowną rejestrację tego samego prefiksu producenta, zgłaszając VendorExtensionRegistryConflictException. CollectionDictionary i CollectionSort modelują wpis katalogu kolekcji PDF (kolekcji przenośnej lub portfolio).

KlasaKluczowe metodyRola
DPartisLeaf(), hasMetadata(), resolveWithPageObjects(), write()Niezmienny węzeł części dokumentu (@since 1.12.0)
DPartRootisEmpty(), write()Korzeń drzewa DPart, który serializuje moduł Writer (@since 1.12.0)
PdfMergermerge(array $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000), append()Scalanie wielu plików PDF z ponownym numerowaniem obiektów (@since 1.9.0)
PdfSplittersplit(), splitEvery(), extractPages()Podział według zakresu stron na SplitDocument (@since 1.9.0)
PageRangecontains(int $page), count(), toArray()Obiekt wartości zakresu stron indeksowany od 1
MergeResult / SplitResultisValid(), count(), document(), totalOutputSize()Obiekty wyników kompozycji
VendorExtensionRegistryrejestracja rozszerzeńRejestr rozszerzeń deweloperskich (@since 2.2.0)
ExtensionsDictionarywithEntry(), entries(), isEmpty(), toPdfDictionary()Niezmienny konstruktor słownika rozszerzeń (@since 2.0.0)
CollectionDictionarytoPdfDictionary()Wpis katalogu kolekcji przenośnej (@since 2.0.0)

Uruchom composer docs:generate-api-php -- --module=Document, aby wygenerować pełną tabelę PHPDoc.

Podziel plik PDF na dokumenty jednostronicowe, a następnie sprawdź wynik.

<?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),
);

Scal kilka plików PDF przy jawnym budżecie wejściowym, a następnie zweryfikuj wynik przed zapisaniem połączonych danych wyjściowych.

<?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() i PdfSplitter::split() wymuszają limity wejścia za pośrednictwem ResourceGuard. Dane wejściowe obejmujące zbyt wiele plików lub zbyt wiele bajtów powodują zgłoszenie wyjątku, zamiast być po cichu obcinane. Ustaw maxFiles / maxTotalBytes rozważnie, odpowiednio do obciążenia.
  • Pusta lista plików lub pusta lista zakresów powoduje zgłoszenie PageLayoutException. Traktuj je jako błędy konfiguracji, a nie puste wyniki.
  • PageRange jest indeksowany od 1 i obejmuje obie granice. Lista pages węzła-liścia DPart zawiera indeksy stron liczone od 0. Te dwie abstrakcje używają różnych baz indeksowania. Przy przechodzeniu między nimi przeliczaj wartości jawnie.
  • DPart jest readonly. Aby zbudować inne drzewo, twórz nowe węzły zamiast modyfikować istniejące. resolveWithPageObjects() zwraca zapasową postać z indeksem całkowitoliczbowym tylko wtedy, gdy mapa obiektów stron jest pusta. Nie polegaj na tej ścieżce w produkcyjnych danych wyjściowych.
  • VendorExtensionRegistry zgłasza VendorExtensionRegistryConflictException dla zduplikowanego prefiksu producenta. Rejestruj każdy prefiks tylko raz.

Podział i scalanie skalują się liniowo wraz z liczbą stron; dominują analiza składniowa i ponowne numerowanie obiektów, a nie wewnętrzna ewidencja modułu. Domyślne obciążenie referencyjne mieści się w budżecie 1500 ms czasu rzeczywistego / 64 MB szczytowego zużycia. Duże operacje scalania są ograniczone głównie przez łączną liczbę bajtów wejścia. Zabezpieczenie maxTotalBytes utrzymuje szczytowe zużycie pamięci w granicach. Profil odtwarzalności ma wartość structural: scalony lub podzielony plik PDF zawiera świeży zwiastun i /ID, dlatego dwa przebiegi są strukturalnie równoważne, ale nie identyczne bajtowo.

PdfMerger::merge() i PdfSplitter::split() przetwarzają niezaufane bajty plików PDF. Przed analizą składniową obie klasy przepuszczają dane wejściowe przez ResourceGuard::assertSize() / assertCount(), co ogranicza atak odmowy usługi oparty na wzmocnieniu dekompresji lub liczbie obiektów. Utrzymuj argumenty maxFiles, maxTotalBytes i maxBytes ściśle dopasowane do wdrożenia, zamiast polegać na wartościach domyślnych. Traktuj każdy wejściowy plik PDF jako wrogi. Gdy źródła pochodzą od użytkownika, uruchamiaj wsadową kompozycję w ograniczonym procesie roboczym. Informacje o granicy zaufania znajdują się w modelu zagrożeń silnika w /modules/core/security/.

Drzewo DPart budowane przez ten moduł jest zgodne z modelem części dokumentu z ISO 32000-2 §14.12, a wpisy /Start i /End liścia są emitowane jako pośrednie odwołania do obiektów stron na podstawie tej samej klauzuli. Scalone dane wyjściowe wykorzystują strukturę węzłów drzewa stron zdefiniowaną w §7.7.3. To fakty implementacyjne wynikające z src/Document/ i weryfikowane przez tests/Unit/Document/ (DPartTest, DPartRootTest, DPartPageRefTest, DocumentPdfMergerDeepTest, DocumentPageRangeParseDeepTest). Nie stanowią deklaracji kompleksowej zgodności z PDF 2.0. Zgodność całego dokumentu jest weryfikowana przez wyrocznię oraz zestawy wzorcowe opisane w /modules/core/conformance/.