Salta ai contenuti

Ispezionare i riquadri di layout prima di posizionare il contenuto

Leggere la geometria di layout corrente di un documento prima di posizionare il contenuto. Questa ricetta interroga il riquadro della pagina, i margini, le eventuali aree non scrivibili e il cursore corrente. La logica di posizionamento può così lavorare su valori reali anziché su coordinate stimate. La ricetta è supportata da examples/34-inspect-layout-boxes.php e dal relativo harness tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php.

Terminal window
composer require nextpdf/core:^3

Non serve alcun pacchetto Pro o Enterprise. La superficie di interrogazione del layout fa parte di Core e funziona con PHP 8.1 fino a 8.4.

Una pagina PDF dispone di riquadri di delimitazione. Il minimo è il MediaBox, il rettangolo che definisce l’estensione della pagina (ISO 32000-2 §7.7.3.3). Il contenuto è posizionato nello spazio utente. Per impostazione predefinita, lo spazio utente ha origine nell’angolo inferiore sinistro e un’unità equivale a 1/72 di pollice (§8.3.2). NextPDF presenta agli autori una vista con origine in alto a sinistra ed espone la geometria tramite metodi di interrogazione di sola lettura:

  • getPageWidth() / getPageHeight() — le dimensioni del riquadro della pagina.
  • getMargins() — l’oggetto valore Margin attivo (top/right/bottom/left).
  • getPageRegions() — le aree non scrivibili dichiarate (PageRegion[]). Ciascuna è un rettangolo immutabile in cui non è consentito posizionare contenuti.
  • getX() / getY() — il cursore attivo nello spazio dell’autore.

Si tratta di letture idempotenti. Non emettono contenuto, non fanno avanzare il cursore e non modificano lo stato. Usarle per calcolare lo spazio verticale rimanente, quindi decidere se continuare a scrivere o chiamare addPage(). È inoltre possibile usarle per collocare un blocco rispetto a un’area non scrivibile anziché a uno scostamento fisso nel codice.

La superficie dell’API è generata da PHPDoc. I punti di accesso principali risiedono nei trait \NextPDF\Core\Concerns\HasPages e 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);

Questo è il programma autonomo eseguibile dall’harness. Rispecchia examples/34-inspect-layout-boxes.php. Legge la geometria della pagina, dichiara un’area non scrivibile per il piè di pagina, quindi decide in base ai dati per ciascun blocco. Se il blocco successivo entrasse in collisione con un’area o oltrepassasse il margine inferiore, aggiunge una pagina anziché sovrastampare.

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

STDOUT atteso. I numeri del riquadro della pagina riflettono il formato pagina A4 predefinito e il cursore parte dall’origine del contenuto in alto a sinistra:

Page box: 595.28 x 841.89 pt
Cursor start: (<x>, <y>)
Pages used: 2 (page breaks decided from layout geometry)
  • Lettura prima che la pagina esista. getPageWidth() e getPageHeight() riflettono la pagina corrente, quindi vanno chiamati dopo addPage(). Prima di aggiungere la prima pagina, restituiscono la geometria del formato pagina predefinito, non quella di una pagina non ancora aggiunta.
  • Le aree sono rettangoli nello spazio dell’autore. Un PageRegiony è la distanza nello spazio dell’autore dall’alto a sinistra, coerente con getY(). Non mescolarlo con le coordinate PDF grezze riferite all’angolo inferiore sinistro.
  • Le letture sono prive di effetti collaterali. Nessuno dei metodi di interrogazione fa avanzare il cursore o emette contenuto. Chiamarli in un ciclo serrato è sicuro e ha un costo trascurabile.
  • I margini possono variare da una pagina all’altra. Se una pagina successiva imposta margini diversi, rileggere getMargins() anziché memorizzare in cache il primo valore.
  • Le aree non ridispongono automaticamente il testo. Un’area non scrivibile è un vincolo che è necessario rispettare, non un limite automatico per il ritorno a capo del testo. La ricetta mostra il controllo esplicito delle collisioni. Con le scritture a posizionamento libero, il motore non sposta mai autonomamente il contenuto fuori da un’area.

Ogni metodo descritto qui è una lettura di proprietà. Viene eseguito in tempo costante, senza allocazioni né operazioni di input/output. L’esempio costruisce un piccolo documento multipagina ben entro il budget di 1500 ms / 96 MB. Il profilo di riproducibilità è semantico, perché il PDF generato contiene il /ID del trailer e i metadati di creazione. Nell’esempio contano le decisioni di geometria osservabili, quindi l’harness le convalida tramite un albero sintattico astratto (AST) strutturale e un confronto dei metadati anziché un hash di byte.

  • Residenza dei dati e mitigazioni delle PII. Non applicabile. La ricetta legge la geometria e dispone il testo fornito dal chiamante, quindi non introduce alcun nuovo percorso dei dati. Applicare al testo inserito la stessa minimizzazione che si adotterebbe in qualsiasi altro contesto.
  • Telemetria sicura e ripulitura dei log. L’esempio stampa i numeri della geometria e una riga di avanzamento fissa. Non scrive nei log il testo del documento.
  • Modello delle minacce. Non applicabile. La superficie di interrogazione è di sola lettura e non analizza alcun input esterno. Non costituisce un confine di attendibilità.
  • Comportamento in modalità FIPS. Non applicabile. Non viene eseguita alcuna operazione crittografica.
DichiarazioneSpecificaClausolareference_id
Un oggetto pagina definisce la propria estensione tramite riquadri di delimitazione (MediaBox).ISO 32000-2§7.7.3.3
Il rettangolo della pagina è il confine del contenuto.ISO 32000-2§7.7.3.3
Le posizioni sono misurate nello spazio utente; un’unità è pari a 1/72 di pollice.ISO 32000-2§8.3.2

Questa ricetta legge la geometria definita dalle clausole ISO 32000-2 citate relative al riquadro della pagina e allo spazio utente. Non dichiara una conformità generale a ISO 32000-2. La superficie di interrogazione del layout si basa sulle clausole citate.