Pular para o conteúdo

Inspecione as caixas de layout antes de posicionar o conteúdo

Leia a geometria de layout ativa de um documento antes de posicionar o conteúdo. Esta receita lê a caixa da página, as margens, as regiões de não escrita e o cursor atual. Assim, a lógica de posicionamento pode usar valores reais em vez de coordenadas adivinhadas. A receita é apoiada por examples/34-inspect-layout-boxes.php e pela estrutura de testes correspondente, tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php.

Terminal window
composer require nextpdf/core:^3

Você não precisa de nenhum pacote Pro ou Enterprise. A interface de consulta de layout faz parte do Core e funciona no PHP 8.1 até 8.4.

Uma página em Portable Document Format (PDF) possui caixas de limite. Entre elas, a mínima é a MediaBox, o retângulo que define a extensão da página (ISO 32000-2 §7.7.3.3). O conteúdo é posicionado no espaço do usuário. Por padrão, o espaço do usuário começa no canto inferior esquerdo, e uma unidade equivale a 1/72 de polegada (§8.3.2). O NextPDF oferece a você uma visão a partir do canto superior esquerdo, mais amigável para autoria, e expõe a geometria por meio de métodos de consulta somente leitura:

  • getPageWidth() / getPageHeight() — dimensões da caixa da página.
  • getMargins() — o objeto de valor Margin ativo (top/right/bottom/left).
  • getPageRegions() — as regiões de não escrita declaradas (PageRegion[]). Cada uma é um retângulo imutável onde o posicionamento de conteúdo é proibido.
  • getX() / getY() — a posição do cursor ativo no espaço do autor.

Essas leituras são idempotentes. Elas não emitem conteúdo, não avançam o cursor nem alteram o estado. Use-as para calcular o espaço vertical restante e, em seguida, decidir se deve continuar escrevendo ou chamar addPage(). Você também pode dispor um bloco em relação a uma região de não escrita em vez de usar um deslocamento fixo codificado.

A application programming interface (API) é gerada a partir do PHPDoc. Os principais pontos de entrada estão nos traits \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);

Este programa autocontido é executado pela estrutura de testes. Ele espelha examples/34-inspect-layout-boxes.php. Ele lê a geometria da página, declara uma região de não escrita para o rodapé e toma uma decisão orientada por dados para cada bloco. Se o próximo bloco colidir com uma região ou ultrapassar a margem inferior, ele adiciona uma página em vez de sobrepor conteúdo.

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

A saída padrão esperada (STDOUT) é esta. Os números da caixa da página refletem o tamanho de página A4 padrão, e o cursor começa na origem do conteúdo no canto superior esquerdo:

Page box: 595.28 x 841.89 pt
Cursor start: (<x>, <y>)
Pages used: 2 (page breaks decided from layout geometry)
  • Leia antes de a página existir. getPageWidth() e getPageHeight() refletem a página atual, então chame-os após addPage(). Antes da primeira página, eles retornam a geometria do tamanho de página padrão, não a de uma página que você ainda não adicionou.
  • As regiões são retângulos no espaço do autor. Um PageRegiony é medido como distância no espaço do autor a partir do canto superior esquerdo, de forma consistente com getY(). Não misture essa medida com coordenadas PDF brutas a partir do canto inferior esquerdo.
  • As leituras são livres de efeitos colaterais. Nenhum dos métodos de consulta avança o cursor nem emite conteúdo. Chamá-los em um laço de alta frequência é seguro e custa pouco.
  • As margens podem mudar por página. Se uma página posterior definir margens diferentes, releia getMargins() em vez de armazenar em cache o primeiro valor.
  • As regiões não reformatam o texto automaticamente. Uma região de não escrita é uma restrição que você deve respeitar, não um limite automático para quebra de texto. A receita mostra a verificação explícita de colisão. Em escritas com posicionamento livre, o engine nunca move o conteúdo para fora de uma região por conta própria.

Cada método descrito aqui faz apenas uma leitura de propriedade. Ele é executado em tempo constante, sem alocação e sem entrada ou saída. O exemplo cria um pequeno documento de várias páginas bem dentro do orçamento de 1500 ms / 96 MB. O perfil de reprodutibilidade é semântico, porque o PDF gerado carrega o /ID do trailer e os metadados de criação. As decisões de geometria observáveis do exemplo são o aspecto relevante; por isso, a estrutura de testes valida essas decisões por meio de uma comparação estrutural de abstract syntax tree (AST) combinada com os metadados, em vez de um hash de bytes.

  • Residência de dados e mitigações de PII. Não aplicável. A receita lê a geometria e posiciona o texto fornecido pelo chamador, portanto não introduz nenhum novo caminho de dados. Aplique ao texto que você posiciona o mesmo princípio de minimização que aplicaria em qualquer outro lugar.
  • Telemetria segura e limpeza de logs. O exemplo imprime números de geometria e uma linha de progresso fixa. Ele não registra em log o texto do documento.
  • Modelo de ameaças. Não aplicável. A interface de consulta é somente leitura e não analisa nenhuma entrada externa. Ela não é um limite de confiança.
  • Comportamento no modo FIPS. Não aplicável. Nenhuma operação criptográfica é executada.
DeclaraçãoEspecificaçãoCláusulareference_id
Um objeto de página define sua extensão por meio de caixas de limite (MediaBox).ISO 32000-2§7.7.3.3
O retângulo da página é o limite do conteúdo.ISO 32000-2§7.7.3.3
As posições são medidas no espaço do usuário; uma unidade é 1/72 de polegada.ISO 32000-2§8.3.2

Esta receita lê a geometria definida pelas cláusulas citadas da ISO 32000-2 sobre caixa de página e espaço do usuário. Ela não declara conformidade integral com a ISO 32000-2. A interface de consulta de layout depende dessas cláusulas.