Een PDF splitsen en paginabereiken extraheren
In één oogopslag
Sectie met titel “In één oogopslag”Je hebt één PDF en je hebt er meerdere nodig. Dit recipe snijdt één document in
meerdere bestanden met de split-interface van Core, NextPDF\Document\PdfSplitter.
Je geeft de bron door als een ruwe PDF-bytereeks en beschrijft welke pagina’s je
wilt. De splitter parset de bron via de objectgraaf, kopieert de bereikbare
objecten van elk aangevraagd bereik naar een verse, hernummerde document met zijn
eigen paginaboom en kruisverwijzingstabel, en levert structureel complete PDF’s
terug die laden in een conforme lezer.
Dit is de inverse van het samenvoeg-recipe: samenvoegen stelt veel documenten samen tot één, splitsen ontleedt één document in vele. Dezelfde interface dekt de drie taken die je het vaakst nodig hebt:
- Splitsen op bereiken — produceer één uitvoerdocument per paginabereik dat je benoemt.
- Splitsen per N pagina’s — knip een lang bestand in segmenten van vaste grootte.
- Een bereik extraheren — trek één aaneengesloten paginabereik in één document.
Het splitsen verloopt in-process, zonder headless browser of netwerkaanroep. Je
hebt Core geïnstalleerd nodig (composer require nextpdf/core:^3) en één leesbare
PDF.
Installeren
Sectie met titel “Installeren”composer require nextpdf/core:^3Conceptueel overzicht
Sectie met titel “Conceptueel overzicht”Een PDF lokaliseert zijn pagina’s via een paginaboom met als wortel een
/Pages-knooppunt, en bereikt elk indirect object via zijn
kruisverwijzingsgegevens (een tabel of een stream). Je kunt pagina’s niet
extraheren door bytes te snijden: één pagina verwijst naar gedeelde lettertypen,
afbeeldingen en resource-dictionaries die elders in het bestand leven, en de
kruisverwijzings-offsets zouden niet langer geldig zijn.
PdfSplitter doet het echte werk. Voor elk bereik loopt het de objectgraaf af
vanaf de aangevraagde pagina-objecten, verzamelt het de bereikbare objectsluiting,
hernummert het die objecten naar een verse adresruimte, herbouwt het een document
met één paginaboom, en zendt het een echte kruisverwijzingstabel uit volgens de
PDF 2.0-structuur (ISO 32000-2:2020, kruisverwijzingstabel §7.5.4, paginaboom
§7.7.3). Elke uitvoer is een op zichzelf staand document, geen fragment.
Paginanummers zijn 1-gebaseerd en inclusief. Een bereik is een
NextPDF\Document\PageRange-waardeobject: new PageRange(2, 5) betekent de
pagina’s 2 tot en met 5. De constructor valideert zijn eigen invarianten — het
weigert een start onder 1 of een einde vóór de start door een
NextPDF\Exception\PageLayoutException op te werpen — zodat een onmogelijk bereik
faalt bij constructie, niet diep in de splitter. PageRange::parse() en
PageRange::all() werpen dezelfde PageLayoutException op bij een misvormde
specificatie of een niet-positief paginatotaal.
API-oppervlak
Sectie met titel “API-oppervlak”new NextPDF\Document\PdfSplitter() stelt drie methoden beschikbaar. Alle nemen
de bron als een ruwe PDF-bytereeks, nooit als een pad.
split(string $pdfData, array $ranges, int $maxBytes = 100_000_000, int $maxRanges = 1000): SplitResultproduceert één uitvoerdocument perPageRangein$ranges, op volgorde. De twee begrenzende parameters limiteren de invoergrootte en het aantal bereiken.splitEvery(string $pdfData, int $pagesPerSegment): SplitResultknipt het document in segmenten van vaste grootte van elk$pagesPerSegmentpagina’s; het laatste segment bevat de rest.extractPages(string $pdfData, PageRange $range): SplitDocumentextraheert één bereik en retourneert dat ene document direct.
split() en splitEvery() retourneren een NextPDF\Document\SplitResult, een
readonly-object dat $documents draagt (een lijst met segmenten), $totalPages
(pagina’s in de bron) en $sourceSize. Het biedt count(),
document(int $index) om een segment op te halen via een nul-gebaseerde index, en
totalOutputSize().
Elk segment, en de retourwaarde van extractPages(), is een
NextPDF\Document\SplitDocument: een readonly-object dat $pdfData (de
segmentbytes), $range, $pageCount, $sizeBytes en de hulpmethode isValid()
blootlegt. isValid() is een nauwe %PDF-header-sanity-check — het retourneert
true wanneer de segmentbytes beginnen met %PDF — geen validatie van
documentstructuur of conformiteit; het bevestigt dat de splitter een PDF heeft
geproduceerd, niet dat het bestand volledig conform is.
Je bouwt een PageRange direct met new PageRange($start, $end), of parset een
mensleesbare specificatie met PageRange::parse('1-3,5,7-10'), die een
list<PageRange> retourneert die klaar is om aan split() door te geven.
PageRange::all($totalPages) retourneert één bereik dat het hele document dekt.
Codevoorbeeld — Snelstart
Sectie met titel “Codevoorbeeld — Snelstart”Dit voorbeeld leest één bestand in en splitst het in twee documenten: de pagina’s 1 tot en met 3, en de pagina’s 4 tot en met 6. Het laat foutafhandeling weg om de aanroepvorm te tonen; het productievoorbeeld hieronder voegt de volledige beveiligingen toe.
<?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());Codevoorbeeld — Productie
Sectie met titel “Codevoorbeeld — Productie”Dit op zichzelf staande programma bouwt één klein document met meerdere pagina’s
in het geheugen, zodat het draait zonder een extern bestand. Het demonstreert alle
drie de operaties — splitsen op bereiken, splitsen per N pagina’s en één bereik
extraheren. Het valideert en schrijft de segmenten per bereik en de geëxtraheerde
staart weg, en rapporteert het resultaat per grootte als een telling, zodat je elke
aanroepvorm ziet zonder drie vrijwel identieke schrijflussen. Het vangt de excepties
die de split-interface opwerpt en gooit elke opnieuw met context in plaats van die
te slikken. Vervang de in-memory bron door je eigen file_get_contents()-lezing of
ophaalactie uit objectopslag.
<?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);}Verwachte standaarduitvoer (bytegroottes hangen af van de build):
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.Randgevallen en valkuilen
Sectie met titel “Randgevallen en valkuilen”- De bron is bytes, geen pad. Elke methode neemt een ruwe PDF-string. Lees het
bestand eerst in met
file_get_contents(), of haal de bytes uit objectopslag. Het doorgeven van een pad zorgt dat de bron niet parset. - Paginanummers zijn 1-gebaseerd en inclusief.
new PageRange(1, 3)dekt de pagina’s 1, 2 en 3 — drie pagina’s. Een start onder 1 of een einde vóór de start werptPageLayoutExceptionop vanuit dePageRange-constructor zelf. - Een bereik voorbij het einde is een fout, geen clamp. Als het einde van een
bereik het paginaaantal van de bron overschrijdt, werpt
split()PageLayoutExceptionop; het trimt het bereik nooit stilzwijgend naar de laatste pagina. Inspecteer het paginaaantal eerst als je bereiken door de aanroeper worden geleverd. splitEvery()behoudt de rest. Het laatste segment bevat welke pagina’s er ook overblijven, dus een document van 7 pagina’s dat elke 2 pagina’s wordt gesplitst, levert vier segmenten op: drie van 2 pagina’s en één van 1.$pagesPerSegmentmoet ten minste 1 zijn, anders krijg je eenInvalidArgumentException.- Een lege bereiklijst wordt geweigerd.
split()met$ranges === []werptInvalidArgumentExceptionop. Bouw ten minste één bereik voordat je het aanroept. - Grenzen werpen op in plaats van af te kappen. Het overschrijden van
maxBytesofmaxRangeswerptInvalidArgumentExceptionop. De splitter verwerkt een te grote invoer nooit gedeeltelijk, dus stem beide grenzen af op je werklast. - Versleutelde, ondertekende en formulierdragende bronnen falen gesloten. Een
versleutelde bron (die kan niet worden gekopieerd zonder de sleutel), een
digitaal ondertekende bron (herpagineren zou de byterange van de handtekening
ongeldig maken), of een bron die een interactief formulier draagt (de widgets van
een veld kunnen op weggelaten pagina’s staan en verweesd raken) werpen
UnsupportedSourceDocumentExceptionop. De splitter weigert liever dan een beschadigd of gecompromitteerd document uit te zenden. Een formulierdocument splitsen is een bekende beperking van deze release. UnsupportedSourceDocumentExceptionleeft onder deMerge-namespace. De volledig gekwalificeerde naam isNextPDF\Document\Merge\UnsupportedSourceDocumentException. DatMerge-pad op een split-pagina is geen kopieer/plak-fout: het is de enige, gedeelde exceptie voor brondocumentweigering die zowel de merge- als de split-interface opwerpen wanneer een bron niet veilig kan worden gekopieerd. Importeer hem uit die namespace.- Uitvoer is structureel vers, niet byte-stabiel. Elk segment is een nieuw
document met zijn eigen catalogus, paginaboom en trailer. Twee runs over dezelfde
invoer zijn structureel gelijk, maar niet gegarandeerd byte-identiek — vandaar het
reproduceerbaarheidsprofiel
structural.
Prestaties
Sectie met titel “Prestaties”Splitsen is lineair in het aantal pagina’s dat over alle bereiken wordt gekopieerd.
Het parsen van de bron en het kopiëren van de objectsluiting van elk bereik, niet
de eigen administratie van de splitter, domineren het werk. De bron wordt als een
string in het geheugen gehouden, en de bytes van elk segment worden vastgehouden
totdat je ze wegschrijft, dus de piekgeheugengebruik volgt de brongrootte plus het
grootste bereik dat je produceert. De maxBytes-guard houdt de bronkant van die
piek begrensd. Stel voor pijplijnen met hoog volume maxBytes en maxRanges in op
de kleinste waarden die je werklast nodig heeft, zodat een misvormde of te grote
invoer snel faalt in plaats van het geheugen uit te putten.
Beveiligingsnotities
Sectie met titel “Beveiligingsnotities”Het splitsen verloopt in-process; geen documentbytes verlaten de host, en er wordt geen netwerkaanroep gedaan. Behandel elke bron-PDF als niet-vertrouwde invoer:
- Houd de grenzen strak.
maxBytesenmaxRangeszijn je eerste verdedigingslinie tegen denial-of-service-invoer. Stel ze voor elke interface die uploads accepteert in op je echte plafond, niet op de royale standaardwaarden. - Triage voordat je splitst. Een bron die versleuteld of ondertekend is, faalt gesloten, maar je kunt die condities eerder detecteren. Voer niet-vertrouwde invoer eerst door de Core-inspector. Zie Een PDF parsen en inspecteren voor een begrensde scan die encryptie, handtekeningen en risicomarkeringen markeert vóór zwaardere verwerking.
- Interpoleer nooit gebruikersinvoer in een pad. Dit recipe schrijft naar een vaste map of het cookbook-zijkanaal. Leid uitvoerpaden en segmentnamen af uit servergecontroleerde waarden, nooit uit een requestveld, om path traversal te voorkomen.
- Geen secrets in de uitvoer. Schrijf geen segmentbestanden naar een locatie, of met een naam, die interne identifiers blootstelt aan een client die ze niet zou mogen zien.
Conformiteit
Sectie met titel “Conformiteit”Dit recipe doet geen eigen normatieve standaardenclaim. Het ontleedt één document
via de split-interface van Core en sanity-checkt elk segment met de
%PDF-header-check van SplitDocument::isValid() — een aanwezigheidscheck dat de
splitter een PDF heeft uitgezonden, geen validatie van conformiteit of
documentstructuur. De paginaboom- en kruisverwijzingsstructuren die PdfSplitter
voor elk segment herbouwt, zijn de PDF 2.0-structuren die zijn beschreven in de
referentie /modules/core/document/ (ISO 32000-2:2020, kruisverwijzingstabel
§7.5.4, paginaboom §7.7.3). Gebruik voor een structurele lezing van een willekeurig
invoer- of uitvoerdocument, inclusief versie, paginaaantal, encryptie- en
handtekeningvlaggen, de Core-inspector die is gedocumenteerd in
Een PDF parsen en inspecteren.
Zie ook
Sectie met titel “Zie ook”- Document-modulereferentie — de volledige split-, merge- en documentdeel-interface.
- Externe PDF’s samenvoegen — het inverse recipe: stel veel documenten samen tot één.
- Een PDF parsen en inspecteren — triage niet-vertrouwde invoer voordat je die splitst.
- Exception-bewuste foutafhandeling
— de NextPDF-exceptiehiërarchie achter
PageLayoutExceptionenUnsupportedSourceDocumentException.