Scalanie zewnętrznych plików PDF i dołączanie stron z istniejących dokumentów
W skrócie
Dział zatytułowany „W skrócie”Masz na dysku kilka plików PDF i potrzebujesz jednego dokumentu PDF. Ten przepis łączy istniejące dokumenty od początku do końca za pomocą interfejsu scalania Core, NextPDF\Document\PdfMerger. Przekazujesz surowe łańcuchy bajtów PDF. Mechanizm scalania przenumerowuje każdy obiekt, aby uniknąć kolizji, buduje jedno drzewo stron i jedną tablicę odsyłaczy oraz zwraca obiekt NextPDF\Document\MergeResult, który możesz zapisać na dysku albo przesłać strumieniowo do klienta.
Ten sam interfejs obsługuje trzy zadania, których potrzebujesz najczęściej:
- Scal uporządkowaną listę plików PDF w jeden dokument.
- Dołącz drugi plik PDF po pliku bazowym.
- Poprzedź istniejące strony, umieszczając nowy dokument jako pierwszy w kolejności wejścia.
Scalanie odbywa się w ramach procesu, bez przeglądarki headless ani wywołania sieciowego. Potrzebujesz zainstalowanego Core (composer require nextpdf/core:^3) oraz dwóch lub więcej możliwych do odczytu plików PDF.
Instalacja
Dział zatytułowany „Instalacja”composer require nextpdf/core:^3Przegląd koncepcyjny
Dział zatytułowany „Przegląd koncepcyjny”Plik PDF porządkuje strony w drzewie stron, którego korzeniem jest węzeł /Pages, a każdy obiekt pośredni odnajduje za pomocą tablicy odsyłaczy. Gdy łączysz dwa dokumenty źródłowe, ich numery obiektów nakładają się na siebie. Oba pliki niemal zawsze zawierają obiekt 1 0 obj, węzeł /Catalog oraz węzeł /Pages. Jeśli tylko połączysz bajty, utworzysz uszkodzony plik, ponieważ odwołania przestaną wskazywać właściwe obiekty.
PdfMerger rozwiązuje to nakładanie się. Wyodrębnia obiekty stron z każdego wejścia, przenumerowuje każdy obiekt do jednej przestrzeni adresowej, przepisuje odwołanie /Parent każdej strony tak, aby wskazywało na pojedynczy scalony węzeł /Pages, oraz emituje jeden katalog, jedno drzewo stron i jeden trailer. Wynikiem jest nowy dokument strukturalny, a nie proste zszycie plików.
Reguła kolejności jest prosta: strony pojawiają się w tej samej kolejności co ich pliki źródłowe na liście wejściowej. Aby dołączyć na końcu, umieść dokument bazowy jako pierwszy. Aby poprzedzić, umieść nowy dokument jako pierwszy. Nie ma osobnej metody poprzedzania, ponieważ wystarcza sama kolejność wejścia.
Interfejs API
Dział zatytułowany „Interfejs API”new NextPDF\Document\PdfMerger() udostępnia dwie metody.
merge(list<string> $pdfFiles, int $maxFiles = 100, int $maxTotalBytes = 200_000_000): MergeResultłączy uporządkowaną listę surowych łańcuchów bajtów PDF. Dwa parametry graniczne ograniczają liczbę plików oraz łączny rozmiar wejścia. Oba mają domyślnie wartości bezpieczne dla produkcji; zaostrz je dla każdego obciążenia.append(string $basePdf, string $appendPdf): MergeResultto wygodna nakładka, która scala dokładnie dwa dokumenty w kolejności. Jest to równoważne wywołaniumerge([$basePdf, $appendPdf]).
Obie metody zwracają NextPDF\Document\MergeResult, obiekt readonly, który zawiera $pdfData (scalone bajty), $totalPages, $sourceCount, $mergedSize oraz funkcję pomocniczą isValid(), która potwierdza, że wynik rozpoczyna się od nagłówka %PDF.
Dane wejściowe to surowe łańcuchy bajtów, a nie ścieżki plików. Odczytaj plik samodzielnie za pomocą file_get_contents() albo pobierz bajty z magazynu obiektów. Dzięki temu mechanizm scalania jest wolny od założeń dotyczących systemu plików i pozwala scalać dokumenty, które nigdy nie trafiają na dysk.
Jeśli potrzebujesz zaimportować pojedynczą stronę z zewnętrznego pliku PDF jako wielokrotnego użytku Form XObject, na przykład aby umieścić stronę z papierem firmowym za generowaną treścią, użyj kontraktu importera między pakietami NextPDF\Contracts\ImportedFormObjectInterface, implementowanego przez importery takie jak nextpdf/artisan. Do komponowania całych dokumentów i całych stron użyj interfejsu PdfMerger opisanego tutaj.
Przykład kodu — szybki start
Dział zatytułowany „Przykład kodu — szybki start”Ten przykład odczytuje dwa pliki i zapisuje scalony wynik. Pomija obsługę błędów, aby pokazać strukturę wywołania; przykład produkcyjny poniżej dodaje pełne zabezpieczenia.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Document\PdfMerger;
$merger = new PdfMerger();
$result = $merger->merge([ file_get_contents(__DIR__ . '/cover.pdf'), file_get_contents(__DIR__ . '/body.pdf'), file_get_contents(__DIR__ . '/appendix.pdf'),]);
file_put_contents(__DIR__ . '/combined.pdf', $result->pdfData);
printf("Merged %d source(s) into %d page(s).\n", $result->sourceCount, $result->totalPages);Przykład kodu — wersja produkcyjna
Dział zatytułowany „Przykład kodu — wersja produkcyjna”Ten samodzielny program buduje dwa małe dokumenty w pamięci, więc działa bez zewnętrznego pliku. Scala je, waliduje wynik i zapisuje rezultat. Przechwytuje dwa wyjątki zgłaszane przez interfejs scalania i ponownie zgłasza każdy z nich z kontekstem, zamiast go ignorować. Zastąp dane wejściowe z pamięci własnymi odczytami file_get_contents() lub pobraniami z magazynu obiektów i podłącz wynik do swojej odpowiedzi albo warstwy magazynowej.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Document\MergeResult;use NextPDF\Document\PdfMerger;use NextPDF\Exception\PageLayoutException;use NextPDF\Exception\WriterException;
/** * Build a tiny labelled PDF so the program is self-contained. * * In your own code, replace calls to this helper with reads of the external * PDFs you want to combine, for example file_get_contents($path). */function buildSample(string $label, int $pages): string{ $doc = Document::createStandalone(); $doc->setTitle($label);
for ($page = 1; $page <= $pages; $page++) { $doc->addPage(); $doc->setFont('helvetica', '', 12); $doc->cell(0, 10, sprintf('%s - page %d', $label, $page), newLine: true); }
return $doc->getPdfData();}
// Validate the input set before touching the merger. An empty set is a// configuration error, not an empty success./** @var list<string> $sources Raw PDF byte strings, in output order. */$sources = [ buildSample('Cover', 1), // first in the list -> first in the output (prepend position) buildSample('Body', 2), buildSample('Appendix', 1), // last in the list -> appended after the body];
if ($sources === []) { throw new RuntimeException('No source PDFs supplied to merge.');}
$merger = new PdfMerger();
try { // Bound the merge deliberately: at most 50 files, 100 MB total input. $result = $merger->merge($sources, maxFiles: 50, maxTotalBytes: 100_000_000);} catch (PageLayoutException $e) { // Raised when the list is empty or an input does not begin with %PDF. throw new RuntimeException( sprintf('Merge rejected an input: %s', $e->getConstraint()), previous: $e, );} catch (WriterException $e) { // Raised when the total input size exceeds the configured byte cap. throw new RuntimeException( sprintf('Merge exceeded its size budget at stage "%s".', $e->getWriterState()), previous: $e, );}
if (!$result->isValid()) { throw new RuntimeException('Merged output failed its structural header check.');}
emitResult($result);
/** * Write the merged document to the cookbook side-channel, or to a default file. */function emitResult(MergeResult $result): void{ printf( "Merged %d source(s) into %d page(s), %d bytes.\n", $result->sourceCount, $result->totalPages, $result->mergedSize, );
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT'); $path = $out !== false && $out !== '' ? $out : __DIR__ . '/combined.pdf';
if (file_put_contents($path, $result->pdfData) === false) { throw new RuntimeException(sprintf('Could not write merged PDF to "%s".', $path)); }}Oczekiwane wyjście standardowe (łączna liczba stron jest sumą liczby stron źródłowych, a rozmiar w bajtach zależy od kompilacji):
Merged 3 source(s) into 4 page(s), <n> bytes.Przypadki brzegowe i pułapki
Dział zatytułowany „Przypadki brzegowe i pułapki”- Dane wejściowe to bajty, a nie ścieżki.
merge()przyjmuje surowe łańcuchy PDF. Najpierw odczytaj plik za pomocąfile_get_contents(). Przekazanie łańcucha ze ścieżką sprawia, że dane wejściowe nie przechodzą sprawdzenia nagłówka%PDF, i powoduje zgłoszeniePageLayoutException. - Kolejność wejścia jest kolejnością wyjścia. Strony trafiają do wyniku w kolejności, w jakiej ich pliki źródłowe pojawiają się na liście. Nie ma metody poprzedzania: aby poprzedzić, umieść nowy dokument jako pierwszy, a aby dołączyć na końcu — jako ostatni.
- Pusta lista to błąd. Pusta wartość
$pdfFilespowoduje zgłoszeniePageLayoutException, a nie pusty wynik. Zwaliduj zestaw, zanim wywołasz mechanizm scalania. - Każdy element wejścia jest walidowany z góry. Każdy wpis musi być niepusty i zaczynać się od
%PDF. Pierwszy błędny element wejścia powoduje zgłoszeniePageLayoutExceptionz naruszonym ograniczeniem i nic nie zostaje scalone. - Przekroczenie limitów kończy się wyjątkiem, nie obcięciem danych. Przekroczenie
maxFilespowoduje zgłoszenie wyjątku przez wewnętrzną ochronę zasobów, a przekroczeniemaxTotalBytespowoduje zgłoszenieWriterException. Mechanizm scalania nigdy po cichu nie odrzuca plików ani nie obcina bajtów, więc dostrój oba limity do swojego obciążenia. - Wynik jest nowy strukturalnie, a nie stabilny bajtowo. Scalony dokument zawiera nowy katalog, drzewo stron i trailer. Dwa przebiegi na tych samych danych wejściowych są równoważne strukturalnie, ale nie ma gwarancji, że będą identyczne bajtowo. Dlatego ten przepis deklaruje profil odtwarzalności
structural. - Adnotacje na poziomie strony i współdzielone zasoby. Scalanie komponuje obiekty stron w jedno drzewo. Struktury na poziomie dokumentu, które w pliku źródłowym znajdują się poza obiektami stron, nie są przenoszone. Gdy musisz zaimportować pojedynczą stronę jako grafikę wielokrotnego użytku wraz z jej zasobami, skorzystaj ze ścieżki
ImportedFormObjectInterfaceprzez importer taki jaknextpdf/artisan.
Wydajność
Dział zatytułowany „Wydajność”Scalanie jest liniowe względem łącznej liczby stron. Najwięcej pracy przypada na parsowanie i przenumerowywanie obiektów, a nie na wewnętrzną ewidencję mechanizmu scalania. Szczytowe zużycie pamięci odpowiada łącznej liczbie bajtów wejścia, ponieważ podczas składania wyniku każde źródło jest przechowywane w pamięci jako łańcuch znaków. Zabezpieczenie maxTotalBytes utrzymuje ten szczyt w granicach. W przypadku potoków o dużym wolumenie ustaw maxFiles oraz maxTotalBytes na najmniejsze wartości, jakich wymaga Twoje obciążenie, aby zniekształcona lub zbyt duża partia szybko zakończyła się błędem, zamiast wyczerpać pamięć. Typowe niewielkie scalanie mieści się w budżecie 1500 ms czasu zegarowego i 64 MB szczytowej pamięci.
Uwagi dotyczące bezpieczeństwa
Dział zatytułowany „Uwagi dotyczące bezpieczeństwa”Scalanie odbywa się w procesie; żadne bajty dokumentu nie opuszczają hosta i nie jest wykonywane żadne wywołanie sieciowe. Każdy zewnętrzny plik PDF traktuj jako niezaufane dane wejściowe:
- Utrzymuj rygorystyczne limity.
maxFilesorazmaxTotalBytesto Twoja pierwsza linia obrony przed danymi wejściowymi powodującymi odmowę usługi. W przypadku każdego interfejsu przyjmującego przesyłane pliki ustaw je na rzeczywisty pułap, a nie na hojne wartości domyślne. - Waliduj, zanim zaufasz. Udane scalanie oznacza, że bajty zostały połączone, a nie że dane wejściowe są bezpieczne. Najpierw przepuść niezaufane dane wejściowe przez inspektor Core. Zobacz Parsowanie i kontrola pliku PDF, aby uzyskać ograniczone skanowanie segregujące, które oznacza szyfrowanie, podpisy i wskaźniki ryzyka przed cięższym przetwarzaniem.
- Nigdy nie wstawiaj danych wejściowych użytkownika do ścieżki. Ten przepis zapisuje do stałej ścieżki lub do kanału pomocniczego receptariusza. Wyznaczaj ścieżki wyjściowe z wartości kontrolowanych przez serwer, nigdy z pola żądania, aby uniknąć przejścia po ścieżce.
- Żadnych sekretów w dokumencie. Nie osadzaj poświadczeń, tokenów ani wewnętrznych identyfikatorów w scalonym dokumencie zwracanym klientowi.
Zgodność
Dział zatytułowany „Zgodność”Ten przepis nie wysuwa żadnego własnego normatywnego twierdzenia o zgodności ze standardami. Komponuje istniejące dokumenty za pomocą interfejsu scalania Core i waliduje wynik za pomocą sprawdzenia nagłówka MergeResult::isValid(). Model drzewa stron odtwarzany przez PdfMerger to struktura drzewa stron PDF 2.0 opisana w dokumentacji /modules/core/document/. Aby uzyskać strukturalny odczyt dowolnego dokumentu wejściowego lub wyjściowego, w tym wersji, liczby stron oraz flag szyfrowania i podpisu, użyj inspektora Core opisanego w Parsowanie i kontrola pliku PDF.
Zobacz też
Dział zatytułowany „Zobacz też”- Dokumentacja modułu Document — pełny interfejs dzielenia, scalania i fragmentów dokumentu.
- Parsowanie i kontrola pliku PDF — przesegreguj niezaufane dane wejściowe, zanim je scalisz.
- Obsługa błędów świadoma wyjątków — hierarchia wyjątków NextPDF stojąca za
PageLayoutExceptionorazWriterException. - Tworzenie dokumentu wielostronicowego — utwórz strony, które następnie połączysz.