Çalışma zamanı belge yapısından içindekiler tablosu oluşturma
Bir bakışta
“Bir bakışta” başlıklı bölümİçeriğiniz çalışma zamanında şekillenebilir: bir veritabanından gelen bölümler, bir API yanıtından gelen kısımlar veya önceden bilemeyeceğiniz bir döngüden gelen başlıklar. Belge ana hattının ve tıklanabilir içindekiler tablosunun bu içerikle tam olarak eşleşmesi gerekir; eşitlemesi bozulup kayabilecek, elle yazılmış ikinci bir liste tutmanıza gerek kalmadan.
Bu tarif ana hattı dinamik olarak oluşturur. Her başlığı yazdığınızda, geçerli imleç ve sayfa bilgisini motordan getPage(), getY() ve getNumPages() ile okur, ardından bu değerleri bookmark() işlevine geçirirsiniz. Yer imi, o anda aldığınız konuma bağlanır; böylece sayfa sonları beklenmedik yerlere düşse bile ana hat içeriği izler. En sonunda addTOC() aynı girdilerden gerçek bir içindekiler tablosu sayfası işler.
Önkoşullar: bir Core kurulumu (composer require nextpdf/core:^3) ve başlık yapısını önceden değil, yazım sırasında keşfettiğiniz içerik.
Bu sayfa dinamik, konuma dayalı deseni kapsar. Her başlığı ve düzeyini önceden bildiğiniz statik senaryo için önce Yer imleri ve bir içindekiler tablosu ekleyin sayfasını okuyun. Bu tarif aynı bookmark() ve addTOC() yüzeyini kullanır ve bu temel bilgileri tekrar etmez.
Kurulum
“Kurulum” başlıklı bölümcomposer require nextpdf/core:^3Herhangi bir isteğe bağlı uzantıya gerek yoktur. Gezinti yüzeyi (bookmark(), addTOC()) ve konum erişimcileri (getPage(), getY(), getNumPages()) 1.2.0 sürümünden bu yana kararlıdır ve 8.1 ile 8.4 arası backport matrisinde çalışır.
Kavramsal genel bakış
“Kavramsal genel bakış” başlıklı bölümDinamik bir içindekiler tablosunda birbiriyle uyumlu kalması gereken iki bölüm vardır:
- Biri ana hat (yer imleri olarak da adlandırılır): okuyucunun gezinti kenar çubuğunda gördüğü ağaç; burada her girdi belgedeki bir konuma atlar.
- Diğeri ise işlenmiş içindekiler tablosu: aynı girdileri sayfa numaralarıyla listeleyen, oluşturulmuş bir sayfa.
NextPDF ikisini de tek bir çağrıyla uyumlu tutar. bookmark($title, $level, $y) bir ana hat öğesi ve bir içindekiler tablosu girdisi ekler; her ikisi de geçerli sayfaya ve geçerli dikey konuma bağlıdır. İki ayrı liste tutmazsınız.
Dinamik olan, konumun nereden geldiğidir. Statik bir tarif, sabit başlıkları kaynak sırasına göre geçirir. Burada bir başlık yazar, ardından hemen motora imlecin nereye düştüğünü sorarsınız:
getPage()etkin sayfanın sıfır tabanlı dizinini döndürür. İlk sayfa eklenmeden önce-1döndürür.getNumPages()henüz boşaltılmamış etkin sayfa da dahil olmak üzere toplam sayfa sayısını döndürür.getY()geçerli dikey imleci, sayfanın üstünden uzaklık olarak ölçülen kullanıcı birimleri cinsinden döndürür.getX(),getPageHeight()vegetMargins()bir başlığın ve gövde metninin ilk satırının birlikte sığıp sığmadığına karar vermeniz gerektiğinde gereken ek bilgiyi sağlar.
Bu değerleri okuyun, ardından bookmark() işlevini çağırın. Otomatik sayfa sonu, imleci iki başlık arasında yeni bir sayfaya taşıyabilir; bu nedenle konumu tekrar okumak, ana hat hedefini doğru sayfada tutar.
Desenin tamamı tek bir sıralama kuralına dayanır: bookmark() işlevini hedefi istediğiniz tam noktada, yani başlık metnini işlemeden hemen önce çağırın. Başlığı önce yazıp yer imini sonradan eklerseniz, kaydedilen getY() doğrudan başlığın altında konumlanır.
API yüzeyi
“API yüzeyi” başlıklı bölümBu tarif şu \NextPDF\Core\Document yöntemlerine dayanır:
bookmark(string $title, int $level = 0, float $y = -1): static- geçerli sayfaya bağlı,$leveldüzeyinde bir ana hat öğesi ve bir içindekiler tablosu girdisi ekler.$y = -1ile hedef, geçerli imleç Y değeridir; kesin bir hedef sabitlemek için negatif olmayan bir Y geçirin.addTOC(int $pageIndex = 0, string $title = ''): static- biriken girdilerden bir içindekiler tablosu sayfası işler ve onu$pageIndexkonumuna ekler. Hiç yer imi yoksa bir sayfa eklemeden döner.getPage(): int- etkin sayfanın sıfır tabanlı dizini (ilk sayfadan önce-1).getNumPages(): int- henüz boşaltılmamış etkin sayfa da dahil olmak üzere toplam sayfa sayısı.getY(): float- kullanıcı birimleri cinsinden geçerli imleç Y değeri (sayfa üstünden uzaklık).getX(): float- kullanıcı birimleri cinsinden geçerli imleç X değeri.getPageHeight(): float- kullanıcı birimleri cinsinden geçerli sayfanın yüksekliği.getMargins(): \NextPDF\ValueObjects\Margin- etkin kenar boşlukları (top,right,bottom,left).setY(float $y): static- imleci belirli bir Y değerine taşır.setAutoPageBreak(bool $enabled, float $margin = 20): static- otomatik sayfa sonunu ve alt kenar boşluğu eşiğini denetler.
Kod örneği — hızlı başlangıç
“Kod örneği — hızlı başlangıç” başlıklı bölümBu örnek, çalışma zamanı listesinden üç kısım yazar. Her yineleme, yer imi eklemeden önce geçerli sayfayı getPage() ile okur; böylece ana hat hedefi otomatik bir sayfa sonundan sonra doğru kalır.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
/** @var list<array{title: string, body: string}> $sections */$sections = [ ['title' => 'Origins', 'body' => 'Runtime content for the first section.'], ['title' => 'Method', 'body' => 'Runtime content for the second section.'], ['title' => 'Results', 'body' => 'Runtime content for the third section.'],];
$doc = Document::createStandalone();$doc->addPage();
foreach ($sections as $section) { // Read the live page back, then bookmark BEFORE rendering the heading, // so the destination points at the heading, not below it. $pageIndex = $doc->getPage(); $doc->bookmark($section['title'], level: 0);
$doc->setFont('helvetica', 'B', 16); $doc->cell(0, 10, $section['title'], newLine: true); $doc->setFont('helvetica', '', 11); $doc->multiCell(0, 7, $section['body']); $doc->ln(6);
echo "Bookmarked '{$section['title']}' on page index {$pageIndex}\n";}
// Splice the rendered table of contents in as the first page.$doc->addTOC(pageIndex: 0, title: 'Contents');
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/dynamic-toc.pdf');Her kısım için bir satır içeren beklenen terminal çıktısı:
Bookmarked 'Origins' on page index 0Bookmarked 'Method' on page index 0Bookmarked 'Results' on page index 0Kod örneği — üretim
“Kod örneği — üretim” başlıklı bölümBu sürüm, iç içe geçmiş bir çalışma zamanı yapısından iki düzeyli bir ana hat (bölümler ve kısımlar) oluşturur. Yazmadan önce konumu okuyarak bir başlığı ilk gövde satırıyla birlikte tutar ve oluşturma işlemini öne çıkan NextPDF özel durumları için try/catch bloklarıyla sarmalar. PageLayoutException oluşturma aşamasındaki bir başarısızlığı, örneğin sayfa üst sınırının aşılmasını kapsar. save() yazılamayan veya güvenli olmayan bir çıkış yolu için InvalidConfigException oluşturur.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;use NextPDF\Exception\PageLayoutException;
/** * Render a report whose chapter and section structure is known only at runtime, * building the outline and table of contents from the live cursor position. * * @param list<array{title: string, sections: list<array{title: string, body: string}>}> $chapters * * @throws PageLayoutException When page generation exceeds an engine limit. * @throws InvalidConfigException When the output path cannot be written. */function renderDynamicToc(array $chapters, string $outputPath): void{ $doc = Document::createStandalone(); $doc->setTitle('Runtime Report'); $doc->setPrintHeader(false); $doc->setPrintFooter(false); // A 25 mm bottom threshold so a heading does not strand at the page foot. $doc->setAutoPageBreak(true, margin: 25); $doc->addPage();
foreach ($chapters as $chapter) { // Reserve space so the chapter heading and its first section start // together: if less than 40 user units remain, break first. $remaining = $doc->getPageHeight() - $doc->getMargins()->bottom - $doc->getY(); if ($remaining < 40.0) { $doc->addPage(); }
// Bookmark at the destination point, before the heading is drawn. $doc->bookmark($chapter['title'], level: 0); $doc->setFont('helvetica', 'B', 18); $doc->cell(0, 12, $chapter['title'], newLine: true); $doc->ln(3);
foreach ($chapter['sections'] as $section) { $doc->bookmark($section['title'], level: 1); $doc->setFont('helvetica', 'B', 13); $doc->cell(0, 9, $section['title'], newLine: true); $doc->setFont('helvetica', '', 11); $doc->multiCell(0, 7, $section['body']); $doc->ln(5); } }
// Render the table of contents only when at least one bookmark exists. // addTOC() is a no-op when the entry list is empty, so an empty report // produces no contents page rather than a blank one. $doc->addTOC(pageIndex: 0, title: 'Table of Contents');
$doc->save($outputPath);}
/** @var list<array{title: string, sections: list<array{title: string, body: string}>}> $chapters */$chapters = [ [ 'title' => 'Chapter 1: Overview', 'sections' => [ ['title' => 'Scope', 'body' => 'Runtime body text for the scope section.'], ['title' => 'Audience', 'body' => 'Runtime body text for the audience section.'], ], ], [ 'title' => 'Chapter 2: Detail', 'sections' => [ ['title' => 'Inputs', 'body' => 'Runtime body text for the inputs section.'], ], ],];
$output = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/dynamic-toc.pdf';
try { renderDynamicToc($chapters, $output); echo "Wrote {$output}\n";} catch (PageLayoutException $e) { // A structural limit was hit during generation; surface the page context. fwrite(STDERR, 'Layout failure while building the report: ' . $e->getMessage() . "\n"); exit(1);} catch (InvalidConfigException $e) { // The output path was rejected (stream wrapper, missing directory, or // a null byte). Report it without leaking the resolved path to a client. fwrite(STDERR, 'Output path rejected: ' . $e->getMessage() . "\n"); exit(1);}Uç durumlar ve tuzaklar
“Uç durumlar ve tuzaklar” başlıklı bölümgetPage()ilk sayfadan önce-1döndürür. Konumu okumadan veyabookmark()işlevini çağırmadan önce ilk sayfayı ekleyin. Örnekler başlangıçta bir sayfa ekler.- Yer imini başlıktan sonra değil, önce ekleyin.
bookmark()işlevi$y = -1ile geçerligetY()değerini kaydeder. Hedefin alttaki satıra değil, başlığa düşmesi için başlık metnini işlemeden hemen önce çağırın. - Otomatik sayfa sonları hedefi taşır.
setAutoPageBreak()açıkken bircell()veyamultiCell()çağrısı yeni bir sayfaya boşaltabilir. Sonraki yinelemedegetPage()değerini önbelleğe almak yerine yeniden okuyun. Hedef, içeriği izler; çünkübookmark()her seferinde canlı konumu okur. - Bir başlık ile ilk satırı için birlikte yer ayırın. Sayfanın alt kısmına sığan, ancak gövdesi sonraki sayfaya kayan bir başlık okuma deneyimini bozar. Üretim örneği kalan yüksekliği
getPageHeight(),getMargins()->bottomvegetY()değerlerinden hesaplar, ardından bir eşikten az kaldığında erken biraddPage()çağrısını zorlar. - Boş bir belgede
addTOC()hiçbir şey yapmaz. Hiçbookmark()çağrısı çalışmadıysaaddTOC()bir sayfa eklemeden döner. Bu nedenle raporu boş girdiye karşı ayrıca korumanız gerekmez; ancak içindekiler sayfasının görünmeyeceğini bilmekte yarar vardır. - İçindekiler tablosu bir kez, onu eklediğiniz konumda işlenir.
addTOC(pageIndex: 0)içindekiler tablosunu ilk sayfa olarak ekler. İşlenmiş girdilerdeki sayfa numaraları her girdinin kaydedilmiş sayfasını kullanır; bu nedenle içindekiler tablosunu tümbookmark()çağrıları çalıştıktan sonra ekleyin. - Düzey atlamaları hatalı biçimlendirilmiş gibi görünür. Ardışık yer imleri arasında
$leveldeğerini en çok bir artırın. Araya bir düzey 1 girmeden düzey 0’dan düzey 2’ye atlamak, bazı okuyucuların yanlış işlediği bir hiyerarşi üretir.
Başarım
“Başarım” başlıklı bölümHer bookmark() çağrısı O(1) zamanda bir ana hat öğesi ve bir içindekiler tablosu girdisi ekler ve her konum okuması (getPage(), getY(), getNumPages()) işleme bağlamında gezinme gerektirmeyen sabit zamanlı bir alan erişimidir. Ana hat ağacı ve içindekiler sayfası ayrı ayrı, birer kez somutlaştırılır: sırasıyla addTOC() ve save() sırasında. Yüzlerce başlık içeren bir rapor, 2000 ms / 64 MB bütçesinin rahatça içinde kalır. Oluşturma işlem içinde çalışır; başsız tarayıcı veya ağ çağrısı gerekmez.
Güvenlik notları
“Güvenlik notları” başlıklı bölümYer imi başlıkları ve içindekiler sayfası, bookmark() işlevine geçirdiğiniz değerleri işler. Bu başlıklar çalışma zamanı verisinden geldiğinde, örneğin bir veritabanı satırından gelen bir bölüm adı veya bir API alanı, dizeyi okuyucuda görüntülenen herhangi bir değerde yaptığınız gibi bookmark() işlevine ulaşmadan önce uzunluk açısından sınırlayın ve temizleyin. Başlıkları doğrulanmamış istek girdisinden oluşturmayın.
Motor, save() işlevine geçirilen çıkış yolunu doğrular: akış sarmalayıcılarını (scheme://) ve gömülü null baytlarını reddeder ve yol geçişini engellemek için üst dizini çözer; bu koşullardan herhangi biri oluşursa InvalidConfigException oluşturur. Bu doğrulamayı, denetiminizdeki bir yolu geçirerek etkin tutun; save() işlevine asla istemci tarafından sağlanan ham bir dosya adı vermeyin. Çağırana InvalidConfigException bildirdiğinizde, ayrıntıyı sunucu tarafında günlüğe kaydedin ve çözülmüş yol yerine genel bir ileti döndürün.
Uygunluk
“Uygunluk” başlıklı bölümBu tarif, kendi başına hiçbir ISO 32000-2 uygunluk iddiasında bulunmaz. Ana hat ve içindekiler tablosu semantiği, belge ana hattının bir ana hat öğeleri ağacı olması ve bu öğelerle ilişkilendirilen hedefler dahil, ilgili madde atıflarını içeren Yer imleri ve bir içindekiler tablosu ekleyin sayfasında açıklanmıştır. Buradaki dinamik desen yalnızca hedef konumun nereden geldiğini değiştirir, yazılan yapıyı değil.
Yeniden üretilebilirlik profili - yapısal. Fragman /ID ve tarih atomları her kaydetmede değişir; yapısal bir karşılaştırma bu değerleri kaldırır. Bu sayfa, NextPDF uygulamasının ana hattı ve içindekileri canlı imleçten nasıl ürettiğini belgeler; genel bir standartlara uygunluk iddiasında bulunmaz.
Ayrıca bakınız
“Ayrıca bakınız” başlıklı bölüm- Yer imleri ve bir içindekiler tablosu ekleyin - bu tarifin statik karşılığı
- Gezinti modülü
- HasPages concern’i - sayfa ve konum yüzeyi
- Çok sayfalı bir belge oluşturma
- Üst bilgiler ve alt bilgiler