Przejdź do głównej zawartości

Sprawdzanie ramek układu przed umieszczaniem treści

Odczytaj bieżącą geometrię układu dokumentu przed umieszczeniem treści. Ten przepis pokazuje, jak odczytać ramkę strony, marginesy, regiony zabronione do zapisu oraz bieżący kursor. Logika rozmieszczania może wtedy korzystać z rzeczywistych wartości, a nie zgadywanych współrzędnych. Przepis opiera się na pliku examples/34-inspect-layout-boxes.php i powiązanym środowisku testowym tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php.

Okno terminala
composer require nextpdf/core:^3

Pakiet Pro ani Enterprise nie jest potrzebny. Interfejs zapytań o układ należy do pakietu Core i działa na PHP od 8.1 do 8.4.

Strona w formacie Portable Document Format (PDF) ma ramki graniczne. Minimalnym wymaganiem jest MediaBox, czyli prostokąt definiujący rozmiar strony (ISO 32000-2 §7.7.3.3). Treść jest pozycjonowana w przestrzeni użytkownika. Domyślnie przestrzeń użytkownika zaczyna się w lewym dolnym rogu, a jedna jednostka odpowiada 1/72 cala (§8.3.2). NextPDF udostępnia autorowi wygodny widok z początkiem w lewym górnym rogu i eksponuje geometrię za pomocą metod zapytań tylko do odczytu:

  • getPageWidth() / getPageHeight() — wymiary ramki strony.
  • getMargins() — aktywny obiekt wartości Margin (góra/prawo/dół/lewo).
  • getPageRegions() — zadeklarowane regiony zabronione do zapisu (PageRegion[]). Każdy z nich jest niezmiennym prostokątem, w którym umieszczanie treści jest zabronione.
  • getX() / getY() — bieżący kursor w przestrzeni autora.

Są to idempotentne odczyty. Nie generują treści, nie przesuwają kursora ani nie zmieniają stanu. Użyj ich, aby obliczyć pozostałe miejsce w pionie, a następnie zdecyduj, czy kontynuować pisanie, czy wywołać addPage(). Możesz też rozmieścić blok względem regionu zabronionego do zapisu zamiast używać przesunięcia zakodowanego na sztywno.

Interfejs programowania aplikacji (API) jest generowany z PHPDoc. Główne punkty wejścia są dostępne w traitach \NextPDF\Core\Concerns\HasPages oraz HasLayout:

  • Document::getPageWidth(): float / Document::getPageHeight(): float
  • Document::getMargins(): \NextPDF\ValueObjects\Margin
  • Document::getPageRegions(): array (list<\NextPDF\Layout\PageRegion>)
  • Document::addPageRegion(float $x, float $y, float $w, float $h): static
  • Document::getX(): float / Document::getY(): float
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->addPage();
$pageHeight = $doc->getPageHeight();
$margins = $doc->getMargins();
$cursorY = $doc->getY();
// Vertical space left before the bottom margin.
$remaining = $pageHeight - $margins->bottom - $cursorY;
// Geometry is in user-space units (PDF points; 1 pt = 1/72 in).
echo sprintf("Page height: %.2f pt\n", $pageHeight);
echo sprintf("Bottom margin: %.2f pt\n", $margins->bottom);
echo sprintf("Cursor Y: %.2f pt\n", $cursorY);
echo sprintf("Remaining: %.2f pt\n", $remaining);

Ten samodzielny program działa w środowisku testowym i odzwierciedla examples/34-inspect-layout-boxes.php. Odczytuje geometrię strony, deklaruje region stopki zabroniony do zapisu i dla każdego bloku podejmuje decyzję na podstawie danych. Jeśli kolejny blok kolidowałby z regionem lub wykroczył poza dolny margines, dodaje stronę zamiast dopuścić do nakładania się treści.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Layout Box Inspection Demo');
$doc->setLanguage('en');
$doc->addPage();
// Read the geometry (idempotent, no side effects). Values are in
// user-space units (PDF points; 1 pt = 1/72 in).
$pageWidth = $doc->getPageWidth();
$pageHeight = $doc->getPageHeight();
$margins = $doc->getMargins();
echo sprintf("Page box: %.2f x %.2f pt\n", $pageWidth, $pageHeight);
echo sprintf("Cursor start: (%.2f, %.2f)\n", $doc->getX(), $doc->getY());
// A 15 pt tall footer no-write zone across the text column.
$footerHeight = 15.0;
$footerTop = $pageHeight - $margins->bottom - $footerHeight;
$doc->addPageRegion(
$margins->left,
$footerTop,
$pageWidth - $margins->left - $margins->right,
$footerHeight,
);
$blocks = [
'Section 1. Each block measures the live cursor against the page box '
. 'and any no-write region before it is placed.',
'Section 2. The lowest Y at which any region starts is the hard floor '
. 'for new content; crossing it forces a page break.',
'Section 3. Reading geometry is idempotent — the query methods never '
. 'advance the cursor, so they are safe in the placement loop.',
'Section 4. This final block forces a second page when the column is '
. 'already near the footer keep-out zone.',
];
$blockHeight = 42.0;
$pagesUsed = 1;
foreach ($blocks as $index => $text) {
$cursorY = $doc->getY();
// Lowest Y a region starts at — the hard floor for new content.
$regionFloor = $pageHeight - $margins->bottom;
foreach ($doc->getPageRegions() as $region) {
$regionFloor = min($regionFloor, $region->y);
}
if ($cursorY + $blockHeight > $regionFloor) {
$doc->addPage();
++$pagesUsed;
// A fresh page resets the region set; re-declare the footer zone.
$footerTop = $doc->getPageHeight() - $doc->getMargins()->bottom - $footerHeight;
$doc->addPageRegion(
$doc->getMargins()->left,
$footerTop,
$doc->getPageWidth() - $doc->getMargins()->left - $doc->getMargins()->right,
$footerHeight,
);
}
$doc->setFont('helvetica', 'B', 12);
$doc->cell(0, 8, 'Block ' . ($index + 1), newLine: true);
$doc->setFont('helvetica', '', 11);
$doc->multiCell(0, 7, $text);
$doc->ln(6);
}
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script under the
// semantic profile (validated by structural AST + metadata, not a byte hash).
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false && $out !== '' ? $out : __DIR__ . '/inspect-layout-boxes.pdf');
echo sprintf("Pages used: %d (page breaks decided from layout geometry)\n", $pagesUsed);

Oczekiwane wyjście standardowe (STDOUT). Wymiary ramki strony odpowiadają domyślnemu rozmiarowi strony A4, a pozycja startowa kursora znajduje się w punkcie początkowym obszaru treści, w lewym górnym rogu:

Page box: 595.28 x 841.89 pt
Cursor start: (<x>, <y>)
Pages used: 2 (page breaks decided from layout geometry)
  • Odczyt przed utworzeniem strony. getPageWidth() i getPageHeight() odzwierciedlają bieżącą stronę, więc wywołuj je po addPage(). Przed dodaniem pierwszej strony zwracają geometrię domyślnego rozmiaru strony, a nie geometrię strony, która nie została jeszcze dodana.
  • Regiony to prostokąty w przestrzeni autora. Wartość PageRegiony jest odległością w przestrzeni autora, liczoną od lewego górnego rogu zgodnie z getY(). Nie mieszaj tej wartości z surowymi współrzędnymi PDF liczonymi od lewego dolnego rogu.
  • Odczyty są wolne od efektów ubocznych. Żadna z metod zapytań nie przesuwa kursora ani nie generuje treści. Wywoływanie ich w ciasnej pętli jest bezpieczne i tanie obliczeniowo.
  • Marginesy mogą się zmieniać na każdej stronie. Jeśli dla kolejnej strony ustawiono inne marginesy, odczytaj getMargins() ponownie zamiast buforować pierwszą wartość.
  • Regiony nie zawijają tekstu automatycznie. Region zabroniony do zapisu to ograniczenie, którego musisz przestrzegać, a nie automatyczna granica zawijania tekstu. Przepis pokazuje jawne sprawdzanie kolizji. Przy zapisach z dowolnym pozycjonowaniem silnik nigdy nie przenosi treści poza region samodzielnie.

Każda z tych metod jest odczytem właściwości. Działa w czasie stałym, bez alokacji oraz bez operacji wejścia i wyjścia. Przykład buduje niewielki dokument wielostronicowy i z dużym zapasem mieści się w budżecie 1500 ms / 96 MB. Profil odtwarzalności jest semantyczny, ponieważ wygenerowany PDF zawiera w trailerze /ID oraz metadane utworzenia. Kluczowe są obserwowalne decyzje geometryczne przykładu, dlatego środowisko testowe weryfikuje je za pomocą strukturalnej weryfikacji abstrakcyjnego drzewa składniowego (AST) oraz porównania metadanych, a nie skrótu bajtowego.

  • Rezydencja danych i środki ograniczające PII. Nie dotyczy. Przepis odczytuje geometrię i rozmieszcza tekst dostarczony przez wywołującego, więc nie wprowadza nowej ścieżki danych. Do umieszczanego tekstu stosuj taką samą minimalizację jak w pozostałych miejscach.
  • Bezpieczna telemetria i czyszczenie logów. Przykład wypisuje wartości geometrii oraz stały komunikat postępu. Nie zapisuje tekstu dokumentu w logach.
  • Model zagrożeń. Nie dotyczy. Interfejs zapytań jest tylko do odczytu i nie parsuje żadnych danych zewnętrznych. Nie stanowi granicy zaufania.
  • Zachowanie w trybie FIPS. Nie dotyczy. Nie jest wykonywana żadna operacja kryptograficzna.
StwierdzenieSpecyfikacjaKlauzulareference_id
Obiekt strony określa swój zakres za pomocą ramek granicznych (MediaBox).ISO 32000-2§7.7.3.3
Prostokąt strony stanowi granicę treści.ISO 32000-2§7.7.3.3
Pozycje są mierzone w przestrzeni użytkownika; jedna jednostka to 1/72 cala.ISO 32000-2§8.3.2

Ten przepis odczytuje geometrię określoną w przytoczonych klauzulach ISO 32000-2 dotyczących ramki strony i przestrzeni użytkownika. Nie deklaruje pełnej zgodności z ISO 32000-2. Interfejs zapytań o układ opiera się na przytoczonych klauzulach.