Ir al contenido

Inspeccionar las cajas de maquetación antes de colocar contenido

Leer la geometría de maquetación activa de un documento antes de colocar contenido. Esta receta consulta la caja de la página, los márgenes, cualquier región sin escritura y el cursor actual. Así, la lógica de colocación trabaja con números reales en lugar de coordenadas estimadas. La receta se apoya en examples/34-inspect-layout-boxes.php y en su arnés tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php.

Ventana de terminal
composer require nextpdf/core:^3

No se necesita ningún paquete Pro ni Enterprise. La superficie de consulta de maquetación forma parte de Core y funciona en PHP 8.1 hasta 8.4.

Una página PDF tiene cajas delimitadoras. La mínima es la MediaBox, el rectángulo que define la extensión de la página (ISO 32000-2 §7.7.3.3). El contenido se posiciona en el espacio de usuario. De forma predeterminada, el espacio de usuario tiene su origen en la esquina inferior izquierda, y una unidad equivale a 1/72 de pulgada (§8.3.2). NextPDF ofrece una vista cómoda para el autor con origen en la esquina superior izquierda y expone la geometría mediante métodos de consulta de solo lectura:

  • getPageWidth() / getPageHeight(): dimensiones de la caja de la página.
  • getMargins(): el objeto de valor Margin activo (superior/derecho/inferior/izquierdo).
  • getPageRegions(): las regiones sin escritura declaradas (PageRegion[]). Cada una es un rectángulo inmutable donde la colocación de contenido está prohibida.
  • getX() / getY(): el cursor activo en el espacio del autor.

Estas consultas son lecturas idempotentes. No emiten contenido, no avanzan el cursor ni cambian el estado. Sirven para calcular el espacio vertical restante y, después, decidir si conviene seguir escribiendo o llamar a addPage(). También permiten maquetar un bloque en relación con una región sin escritura en lugar de depender de un desplazamiento fijo en el código.

La superficie de la API se genera a partir de PHPDoc. Los principales puntos de entrada residen en los traits \NextPDF\Core\Concerns\HasPages y 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);

Este es el programa autocontenido que ejecuta el arnés. Corresponde a examples/34-inspect-layout-boxes.php. Lee la geometría de la página, declara una región sin escritura para el pie de página y luego toma una decisión basada en datos para cada bloque. Si el siguiente bloque chocara con una región o sobrepasara el margen inferior, se agrega una página en lugar de escribir encima.

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

Salida STDOUT esperada. Los números de la caja de la página reflejan el tamaño de página A4 predeterminado, y el cursor empieza en el origen de contenido superior izquierdo:

Page box: 595.28 x 841.89 pt
Cursor start: (<x>, <y>)
Pages used: 2 (page breaks decided from layout geometry)
  • Leer antes de que exista la página. getPageWidth() y getPageHeight() reflejan la página actual, así que conviene llamarlos después de addPage(). Antes de la primera página, devuelven la geometría del tamaño de página predeterminado, no la de una página que aún no se ha agregado.
  • Las regiones son rectángulos en el espacio del autor. El valor de PageRegiony se mide como distancia desde la parte superior izquierda en el espacio del autor, de forma coherente con getY(). No se debe mezclar esa medida con coordenadas PDF crudas de la esquina inferior izquierda.
  • Las lecturas no tienen efectos secundarios. Ninguno de los métodos de consulta avanza el cursor ni emite contenido. Llamarlos dentro de un bucle interno es seguro y cuesta poco.
  • Los márgenes pueden cambiar por página. Si en una página posterior se establecen márgenes distintos, hay que volver a leer getMargins() en lugar de almacenar en caché el primer valor.
  • Las regiones no reajustan el texto automáticamente. Una región sin escritura es una restricción que debes respetar, no un límite automático de ajuste de texto. La receta muestra la comprobación explícita de colisiones. En escrituras de posición libre, el motor nunca desplaza el contenido fuera de una región por su cuenta.

Cada método descrito aquí es una lectura de propiedad. Se ejecuta en tiempo constante, sin asignar memoria y sin entrada ni salida. El ejemplo construye un documento pequeño de varias páginas, con holgura dentro del presupuesto de 1500 ms / 96 MB. El perfil de reproducibilidad es semántico, porque el PDF generado incluye el /ID del tráiler y los metadatos de creación. Lo relevante del ejemplo son sus decisiones de geometría observables, así que el arnés valida esas decisiones mediante una comparación estructural del árbol de sintaxis abstracta (AST), junto con los metadatos, en lugar de un hash de bytes.

  • Residencia de datos y mitigaciones de PII. No aplica. La receta lee geometría y maqueta texto proporcionado por quien la invoca, así que no introduce ninguna nueva ruta de datos. Al texto que se coloque se le debe aplicar la misma minimización que en cualquier otro lugar.
  • Telemetría segura y depuración de registros. El ejemplo imprime números de geometría y una línea de progreso fija. No registra el texto del documento.
  • Modelo de amenazas. No aplica. La superficie de consulta es de solo lectura y no analiza ninguna entrada externa. No constituye un límite de confianza.
  • Comportamiento en modo FIPS. No aplica. No se ejecuta ninguna operación criptográfica.
DeclaraciónEspecificaciónCláusulareference_id
Un objeto de página define su extensión mediante cajas delimitadoras (MediaBox).ISO 32000-2§7.7.3.3
El rectángulo de la página es el límite del contenido.ISO 32000-2§7.7.3.3
Las posiciones se miden en el espacio de usuario; una unidad es 1/72 de pulgada.ISO 32000-2§8.3.2

Esta receta lee la geometría que definen las cláusulas citadas de ISO 32000-2 sobre caja de la página y espacio de usuario. No afirma conformidad general con ISO 32000-2. Las cláusulas citadas son aquellas en las que se apoya la superficie de consulta de maquetación.