Zum Inhalt springen

Layout-Boxen prüfen, bevor Sie Inhalt platzieren

Lesen Sie die Live-Layout-Geometrie eines Dokuments aus, bevor Sie Inhalt platzieren. Dieses Recipe fragt die Page-Box, die Ränder, etwaige No-Write-Regionen und den aktuellen Cursor ab. Ihre Platzierungslogik arbeitet dadurch mit tatsächlichen Werten statt mit geratenen Koordinaten. Das Recipe ist durch examples/34-inspect-layout-boxes.php und den zugehörigen tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php-Harness abgedeckt.

Terminal-Fenster
composer require nextpdf/core:^3

Sie brauchen kein Pro- oder Enterprise-Paket. Die Layout-Abfrage-Oberfläche ist Teil von Core und läuft auf PHP 8.1 bis 8.4.

Eine PDF-Seite hat Begrenzungsboxen. Die Mindestangabe ist die MediaBox, also das Rechteck, das die Seitenausdehnung definiert (ISO 32000-2 §7.7.3.3). Inhalt wird im User-Space positioniert. Standardmäßig hat der User-Space seinen Ursprung in der unteren linken Ecke, und eine Einheit entspricht 1/72 Zoll (§8.3.2). NextPDF bietet eine autorenfreundliche Sicht von oben links und stellt die Geometrie über schreibgeschützte Abfragemethoden bereit:

  • getPageWidth() / getPageHeight() — die Abmessungen der Page-Box.
  • getMargins() — das aktive Margin-Value-Object (oben/rechts/unten/links).
  • getPageRegions() — die deklarierten No-Write-Zonen (PageRegion[]). Jede Zone ist ein unveränderliches Rechteck, in dem Inhalt nicht platziert werden darf.
  • getX() / getY() — der Live-Cursor im Autoren-Space.

Diese Methoden sind idempotente Lesezugriffe. Sie geben keinen Inhalt aus, rücken den Cursor nicht vor und ändern keinen Zustand. Nutzen Sie sie, um den verbleibenden vertikalen Platz zu berechnen, und entscheiden Sie dann, ob Sie weiterschreiben oder addPage() aufrufen. Sie können sie auch verwenden, um einen Block relativ zu einer No-Write-Region statt zu einem fest codierten Offset zu layouten.

Die API-Oberfläche wird aus PHPDoc generiert. Die zentralen Einstiegspunkte befinden sich in den Traits \NextPDF\Core\Concerns\HasPages und 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);

Das folgende eigenständige Programm ist im Harness ausführbar. Es spiegelt examples/34-inspect-layout-boxes.php wider. Es liest die Seitengeometrie, deklariert eine No-Write-Region für die Fußzeile und trifft dann für jeden Block eine datengetriebene Entscheidung. Wenn der nächste Block mit einer Region kollidieren oder über den unteren Rand hinauslaufen würde, fügt das Programm eine Seite hinzu, statt Inhalt zu überdrucken.

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

Erwartete STDOUT-Ausgabe. Die Werte der Page-Box entsprechen der voreingestellten A4-Seitengröße; der Cursor startet am oberen linken Inhaltsursprung:

Page box: 595.28 x 841.89 pt
Cursor start: (<x>, <y>)
Pages used: 2 (page breaks decided from layout geometry)
  • Vor der Existenz der Seite lesen. getPageWidth() und getPageHeight() spiegeln die aktuelle Seite wider; rufen Sie sie also nach addPage() auf. Vor der ersten Seite geben sie die Geometrie der voreingestellten Seitengröße zurück, nicht die Geometrie einer Seite, die Sie noch nicht hinzugefügt haben.
  • Regionen sind Rechtecke im Autoren-Space. Der Wert PageRegiony gibt im Autoren-Space den Abstand von oben links an und ist damit konsistent mit getY(). Mischen Sie ihn nicht mit rohen PDF-Koordinaten von unten links.
  • Lesezugriffe sind seiteneffektfrei. Keine der Abfragemethoden rückt den Cursor vor oder gibt Inhalt aus. Der Aufruf in einer engen Schleife ist sicher und kostet wenig.
  • Ränder können sich pro Seite ändern. Wenn für eine spätere Seite andere Ränder gesetzt werden, lesen Sie getMargins() erneut, statt den ersten Wert zu cachen.
  • Regionen lassen Text nicht automatisch neu umfließen. Eine No-Write-Region ist eine Vorgabe, die Sie einhalten müssen, keine automatische Grenze für den Textumbruch. Das Recipe zeigt die explizite Kollisionsprüfung. Bei frei positionierten Schreibvorgängen verschiebt die Engine Inhalt niemals von selbst aus einer Region heraus.

Jede Methode hier ist ein Property-Lesezugriff. Sie läuft in konstanter Zeit, ohne Allokationen und ohne Ein- oder Ausgabe. Das Beispiel baut ein kleines mehrseitiges Dokument deutlich innerhalb des Budgets von 1500 ms / 96 MB. Das Reproduzierbarkeitsprofil ist semantisch, denn das erzeugte PDF trägt den Trailer /ID und Erstellungsmetadaten. Entscheidend für das Beispiel sind die beobachtbaren Geometrieentscheidungen; deshalb validiert der Harness diese Entscheidungen über einen strukturellen abstrakten Syntaxbaum (AST) plus Metadatenvergleich statt über einen Byte-Hash.

  • Datenresidenz & PII-Minderung. Nicht zutreffend. Das Recipe liest Geometrie aus und layoutet vom Aufrufer gelieferten Text; es führt also keinen neuen Datenpfad ein. Wenden Sie auf den Text, den Sie platzieren, dieselbe Minimierung an wie überall sonst.
  • Sichere Telemetrie & Log-Bereinigung. Das Beispiel gibt Geometriezahlen und eine feste Fortschrittszeile aus. Dokumenttext wird nicht geloggt.
  • Bedrohungsmodell. Nicht zutreffend. Die Abfrage-Oberfläche ist schreibgeschützt und parst keine externe Eingabe. Sie bildet keine Vertrauensgrenze.
  • FIPS-Modus-Verhalten. Nicht zutreffend. Es findet keine kryptografische Operation statt.
AussageSpezifikationKlauselreference_id
Ein Seitenobjekt definiert seine Ausdehnung über Begrenzungsboxen (MediaBox).ISO 32000-2§7.7.3.3
Das Seitenrechteck ist die Inhaltsgrenze.ISO 32000-2§7.7.3.3
Positionen werden im User-Space gemessen; eine Einheit ist 1/72 Zoll.ISO 32000-2§8.3.2

Dieses Recipe liest die Geometrie, die die zitierten Klauseln zu Page-Box und User-Space von ISO 32000-2 definieren. Es behauptet keine pauschale ISO 32000-2-Konformität. Die zitierten Klauseln sind die, auf die sich die Layout-Abfrage-Oberfläche stützt.