İçeriğe geç
getnextpdf.com

Bir PDF'i bölün ve sayfa aralıklarını çıkarın

Tek bir PDF’iniz var ve birden fazlasına ihtiyacınız var. Bu tarif, Core bölme yüzeyi olan NextPDF\Document\PdfSplitter ile tek bir belgeyi birden çok dosyaya ayırır. Kaynağı ham bir PDF bayt dizesi olarak geçirir ve hangi sayfaları istediğinizi tarif edersiniz. Bölücü, kaynağı nesne grafiği üzerinden ayrıştırır, istenen her aralığın erişilebilir nesnelerini kendi sayfa ağacına ve çapraz referans tablosuna sahip taze, yeniden numaralandırılmış bir belgeye kopyalar ve uyumlu bir okuyucuda açılan, yapısal olarak eksiksiz PDF’leri geri verir.

Bu, birleştirme tarifinin tersidir: birleştirme birçok belgeyi tek bir belgede oluşturur, bölme bir belgeyi birçok belgeye ayrıştırır. Aynı yüzey, en sık ihtiyaç duyduğunuz üç görevi kapsar:

  • Aralıklara göre bölme — adlandırdığınız her sayfa aralığı için bir çıktı belgesi üretin.
  • Her N sayfada bir bölme — uzun bir dosyayı sabit boyutlu parçalara kesin.
  • Bir aralığı çıkarma — tek bir bitişik sayfa aralığını tek bir belgeye alın.

Bölme işlemi süreç içinde, başsız tarayıcı veya ağ çağrısı olmadan çalışır. Core’un kurulu olması (composer require nextpdf/core:^3) ve okunabilir bir PDF gerekir.

Terminal window
composer require nextpdf/core:^3

Bir PDF, sayfalarına bir /Pages düğümünde köklenen bir sayfa ağacı üzerinden ulaşır ve her dolaylı nesneye çapraz referans verisi (bir tablo veya bir akış) aracılığıyla erişir. Sayfaları bayt dilimleyerek çıkaramazsınız: tek bir sayfa, dosyanın başka bir yerinde bulunan paylaşılan yazı tiplerine, görüntülere ve kaynak sözlüklerine atıfta bulunur ve çapraz referans konumları artık geçerli olmaz.

PdfSplitter asıl işi yapar. Her aralık için, istenen sayfa nesnelerinden nesne grafiğini gezer, erişilebilir nesne kapanışını toplar, bu nesneleri taze bir adres alanında yeniden numaralandırır, tek sayfa ağaçlı bir belge yeniden inşa eder ve PDF 2.0 yapısına uygun gerçek bir çapraz referans tablosu yayar (ISO 32000-2:2020, çapraz referans tablosu §7.5.4, sayfa ağacı §7.7.3). Her çıktı, bir parça değil, kendi kendine yeten bir belgedir.

Sayfa numaraları 1 tabanlı ve dâhildir. Bir aralık, bir NextPDF\Document\PageRange değer nesnesidir: new PageRange(2, 5), 2’den 5’e kadarki sayfalar anlamına gelir. Yapıcı, kendi değişmezlerini doğrular — NextPDF\Exception\PageLayoutException fırlatarak 1’in altındaki bir başlangıcı veya başlangıçtan önce gelen bir sonu reddeder — böylece imkânsız bir aralık, bölücünün derinlerinde değil, oluşturma anında başarısız olur. PageRange::parse() ve PageRange::all(), hatalı biçimlendirilmiş bir belirtimde veya pozitif olmayan bir sayfa toplamında aynı PageLayoutException’ı fırlatır.

new NextPDF\Document\PdfSplitter() üç yöntem sunar. Hepsi kaynağı, asla bir yol değil, ham bir PDF bayt dizesi olarak alır.

  • split(string $pdfData, array $ranges, int $maxBytes = 100_000_000, int $maxRanges = 1000): SplitResult $ranges içindeki her PageRange için sırayla bir çıktı belgesi üretir. İki sınır parametresi, girdi boyutunu ve aralık sayısını sınırlandırır.
  • splitEvery(string $pdfData, int $pagesPerSegment): SplitResult belgeyi her biri $pagesPerSegment sayfa olan sabit boyutlu parçalara keser; son parça kalanı tutar.
  • extractPages(string $pdfData, PageRange $range): SplitDocument tek bir aralığı çıkarır ve doğrudan o tek belgeyi döndürür.

split() ve splitEvery(), bir readonly nesne olan ve $documents (parçaların bir listesi), $totalPages (kaynaktaki sayfalar) ve $sourceSize taşıyan bir NextPDF\Document\SplitResult döndürür. count(), sıfır tabanlı bir dizine göre bir parça getirmek için document(int $index) ve totalOutputSize() sunar.

Her parça ve extractPages()’in dönüş değeri, bir NextPDF\Document\SplitDocument nesnesidir: $pdfData (parça baytları), $range, $pageCount, $sizeBytes ve isValid() yardımcısını sunan bir readonly nesne. isValid() dar bir %PDF-başlık akıl sağlığı denetimidir — parça baytları %PDF ile başladığında true döndürür — bir belge yapısı veya uyumluluk doğrulaması değildir; bölücünün bir PDF ürettiğini onaylar, dosyanın tam olarak uyumlu olduğunu değil.

Bir PageRange’i doğrudan new PageRange($start, $end) ile oluşturur ya da insan tarafından okunabilir bir belirtimi PageRange::parse('1-3,5,7-10') ile ayrıştırırsınız; bu, split()’e iletmeye hazır bir list<PageRange> döndürür. PageRange::all($totalPages), belgenin tamamını kapsayan tek bir aralık döndürür.

Bu örnek tek bir dosyayı okur ve onu iki belgeye böler: 1’den 3’e kadarki sayfalar ve 4’ten 6’ya kadarki sayfalar. Çağrı şeklini göstermek için hata işlemeyi atlar; aşağıdaki üretim örneği tüm korumaları ekler.

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

Bu kendi kendine yeten program, bellekte küçük bir çok sayfalı belge oluşturur, böylece harici bir dosya olmadan çalışır. Üç işlemin tümünü gösterir — aralıklara göre bölme, her N sayfada bir bölme ve tek bir aralığı çıkarma. Aralığa göre parçaları ve çıkarılan kuyruğu doğrulayıp yazar ve boyuta göre sonucu bir sayım olarak bildirir, böylece neredeyse aynı üç yazma döngüsü olmadan her çağrı şeklini görürsünüz. Bölme yüzeyinin fırlattığı istisnaları yakalar ve her birini yutmak yerine bağlamla yeniden fırlatır. Bellek içi kaynağı kendi file_get_contents() okumanız veya nesne deposu getirmeniz ile değiştirin.

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

Beklenen standart çıktı (bayt boyutları derlemeye bağlıdır):

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.
  • Kaynak bir yol değil, bayttır. Her yöntem ham bir PDF dizesi alır. Dosyayı önce file_get_contents() ile okuyun ya da baytları nesne deposundan çekin. Bir yol geçirmek, kaynağın ayrıştırılamamasına yol açar.
  • Sayfa numaraları 1 tabanlı ve dâhildir. new PageRange(1, 3), 1, 2 ve 3. sayfaları kapsar — üç sayfa. 1’in altındaki bir başlangıç veya başlangıçtan önce gelen bir son, PageRange yapıcısının kendisinden PageLayoutException fırlatır.
  • Sonun ötesindeki bir aralık bir hatadır, bir kırpma değil. Bir aralığın sonu kaynak sayfa sayısını aşarsa, split() PageLayoutException fırlatır; aralığı asla sessizce son sayfaya kırpmaz. Aralıklarınız çağıran tarafından sağlanıyorsa önce sayfa sayısını inceleyin.
  • splitEvery() kalanı tutar. Son parça geriye kalan sayfaları tutar; bu nedenle her 2 sayfada bir bölünen 7 sayfalık bir belge dört parça verir: üçü 2 sayfalık ve biri 1 sayfalık. $pagesPerSegment en az 1 olmalıdır, aksi takdirde bir InvalidArgumentException alırsınız.
  • Boş bir aralık listesi reddedilir. $ranges === [] ile split() bir InvalidArgumentException fırlatır. Çağırmadan önce en az bir aralık oluşturun.
  • Sınırlar kesmek yerine fırlatır. maxBytes veya maxRanges değerinin aşılması bir InvalidArgumentException fırlatır. Bölücü, aşırı boyutlu bir girdiyi asla kısmen işlemez; bu nedenle her iki sınırı da iş yükünüze göre ayarlayın.
  • Şifrelenmiş, imzalanmış ve form taşıyan kaynaklar kapalı arıza verir. Şifrelenmiş bir kaynak (anahtar olmadan kopyalanamaz), dijital olarak imzalanmış bir kaynak (yeniden sayfalandırma, imzanın bayt aralığını geçersiz kılar) ya da etkileşimli bir form taşıyan bir kaynak (bir alanın araç birimleri düşürülen sayfalarda yer alıp öksüz kalabilir) UnsupportedSourceDocumentException fırlatır. Bölücü, bozuk veya tehlikeye girmiş bir belge yaymaktansa reddeder. Bir form belgesini bölmek, bu sürümün bilinen bir kısıtlamasıdır.
  • UnsupportedSourceDocumentException, Merge ad alanı altında bulunur. Tam nitelenmiş adı NextPDF\Document\Merge\UnsupportedSourceDocumentException’dır. Bir bölme sayfasındaki o Merge yolu bir kopyala/yapıştır hatası değildir: bir kaynak güvenle kopyalanamadığında hem birleştirme hem de bölme yüzeylerinin fırlattığı tek, paylaşılan kaynak-belge reddetme istisnasıdır. Onu o ad alanından içe aktarın.
  • Çıktı yapısal olarak tazedir, bayt kararlı değildir. Her parça kendi kataloğuna, sayfa ağacına ve fragmanına sahip yeni bir belgedir. Aynı girdi üzerinde yapılan iki çalıştırma yapısal olarak eşittir, ancak baytça aynı olacağı garanti edilmez — bu nedenle structural yeniden üretilebilirlik profili.

Bölme, tüm aralıklarda kopyalanan sayfa sayısıyla doğrusaldır. İşin baskın kısmı, bölücünün kendi defter tutması değil, kaynağın ayrıştırılması ve her aralığın nesne kapanışının kopyalanmasıdır. Kaynak bellekte bir dize olarak tutulur ve her parçanın baytları siz yazana kadar tutulur; bu nedenle tepe bellek, kaynak boyutu artı ürettiğiniz en büyük aralık kadar olur. maxBytes koruması, bu tepenin kaynak tarafını sınırlı tutar. Yüksek hacimli işlem hatları için, hatalı biçimlendirilmiş veya aşırı boyutlu bir girdinin belleği tüketmek yerine hızlıca başarısız olması için maxBytes ve maxRanges değerlerini iş yükünüzün ihtiyaç duyduğu en küçük değerlere ayarlayın.

Bölme süreç içinde çalışır; hiçbir belge baytı ana makineden ayrılmaz ve hiçbir ağ çağrısı yapılmaz. Her kaynak PDF’i güvenilmeyen girdi olarak ele alın:

  • Sınırları sıkı tutun. maxBytes ve maxRanges, hizmet reddi girdisine karşı ilk savunma hattınızdır. Yüklemeleri kabul eden herhangi bir yüzey için bunları cömert varsayılanlara değil, gerçek tavanınıza ayarlayın.
  • Bölmeden önce ayıklayın. Şifrelenmiş veya imzalanmış bir kaynak kapalı arıza verir, ancak bu koşulları daha erken saptayabilirsiniz. Güvenilmeyen girdileri önce Core denetçisinden geçirin. Daha ağır işlemden önce şifrelemeyi, imzaları ve risk işaretlerini bayraklayan sınırlı bir tarama için Bir PDF’i ayrıştırın ve inceleyin sayfasına bakın.
  • Kullanıcı girdisini asla bir yola eklemeyin. Bu tarif, sabit bir dizine veya cookbook yan kanalına yazar. Yol geçişini önlemek için çıktı yollarını ve parça adlarını bir istek alanından değil, sunucu tarafından denetlenen değerlerden türetin.
  • Çıktıda gizli bilgi yok. Parça dosyalarını, görmemesi gereken bir istemciye iç tanımlayıcıları ortaya çıkaran bir konuma ya da adla yazmayın.

Bu tarif, kendine ait normatif bir standart iddiasında bulunmaz. Tek bir belgeyi Core bölme yüzeyi üzerinden ayrıştırır ve her parçayı SplitDocument::isValid() %PDF-başlık denetimiyle akıl sağlığı açısından denetler — bölücünün bir PDF yaydığına dair bir varlık denetimi, bir uyumluluk veya belge yapısı doğrulaması değil. PdfSplitter’ın her parça için yeniden inşa ettiği sayfa ağacı ve çapraz referans yapıları, /modules/core/document/ referansında tarif edilen PDF 2.0 yapılarıdır (ISO 32000-2:2020, çapraz referans tablosu §7.5.4, sayfa ağacı §7.7.3). Sürüm, sayfa sayısı, şifreleme ve imza bayrakları dâhil olmak üzere herhangi bir girdi veya çıktı belgesinin yapısal okuması için Bir PDF’i ayrıştırın ve inceleyin sayfasında belgelenen Core denetçisini kullanın.