콘텐츠로 이동

콘텐츠 배치 전에 레이아웃 박스 검사하기

콘텐츠를 배치하기 전에 문서의 실시간 레이아웃 지오메트리를 읽습니다. 이 레시피에서는 페이지 박스, 여백, 쓰기 금지 영역, 현재 커서를 조회합니다. 이를 통해 배치 로직은 추정 좌표가 아니라 실제 수치를 기반으로 동작합니다. 이 레시피는 examples/34-inspect-layout-boxes.php 및 해당 tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php 하네스가 뒷받침합니다.

Terminal window
composer require nextpdf/core:^3

Pro 또는 Enterprise 패키지는 필요하지 않습니다. 레이아웃 조회 기능은 Core의 일부이며, PHP 8.1부터 8.4까지 실행됩니다.

PDF 페이지에는 경계 박스가 있습니다. 최소한 페이지 범위를 정의하는 사각형인 MediaBox가 있습니다(ISO 32000-2 §7.7.3.3). 콘텐츠 위치는 사용자 공간에서 지정됩니다. 기본 사용자 공간에서는 원점이 왼쪽 아래 모서리에 있고, 1 단위는 1/72 인치와 같습니다(§8.3.2). NextPDF는 작성자에게 익숙한 왼쪽 위 기준 뷰를 제공하고, 읽기 전용 조회 메서드로 지오메트리를 노출합니다:

  • getPageWidth() / getPageHeight() — 페이지 박스의 치수입니다.
  • getMargins() — 현재 적용 중인 Margin 값 객체입니다(위/오른쪽/아래/왼쪽).
  • getPageRegions() — 선언된 쓰기 금지 영역입니다(PageRegion[]). 각 영역은 콘텐츠 배치가 금지된 불변 사각형입니다.
  • getX() / getY() — 작성자 공간의 실시간 커서입니다.

이 메서드들은 멱등적 읽기입니다. 콘텐츠를 출력하거나 커서를 전진시키거나 상태를 변경하지 않습니다. 이를 사용해 남은 수직 공간을 계산한 다음, 계속 쓸지 아니면 addPage()를 호출할지 결정하십시오. 또한 하드코딩된 오프셋 대신 쓰기 금지 영역을 기준으로 블록을 배치할 수도 있습니다.

API 표면은 PHPDoc에서 생성됩니다. 주요 진입점은 \NextPDF\Core\Concerns\HasPagesHasLayout 트레이트에 있습니다:

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

다음은 자체 완결형이며 하네스에서 실행할 수 있는 프로그램입니다. examples/34-inspect-layout-boxes.php를 그대로 반영합니다. 페이지 지오메트리를 읽고, 푸터 쓰기 금지 영역을 선언한 다음, 각 블록에 대해 데이터 기반 결정을 내립니다. 다음 블록이 영역과 충돌하거나 아래쪽 여백을 넘게 되면 겹쳐 인쇄하지 않고 페이지를 추가합니다.

<?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입니다. 페이지 박스 수치는 기본 A4 페이지 크기를 반영하고, 커서는 왼쪽 위 콘텐츠 원점에서 시작합니다:

Page box: 595.28 x 841.89 pt
Cursor start: (<x>, <y>)
Pages used: 2 (page breaks decided from layout geometry)
  • 페이지가 존재한 뒤에 읽으십시오. getPageWidth()getPageHeight()현재 페이지를 반영하므로, addPage() 이후에 호출하십시오. 첫 페이지가 추가되기 전에는 아직 추가되지 않은 페이지가 아니라 기본 페이지 크기 지오메트리를 반환합니다.
  • 영역은 작성자 공간 사각형입니다. PageRegiony는 왼쪽 위 기준의 작성자 공간 거리이며, getY()와 일관됩니다. 원시 왼쪽 아래 PDF 좌표와 혼용하지 마십시오.
  • 읽기는 부작용이 없습니다. 조회 메서드 중 어느 것도 커서를 전진시키거나 콘텐츠를 출력하지 않습니다. 반복 횟수가 많은 루프에서 호출해도 안전하며 비용이 거의 들지 않습니다.
  • 여백은 페이지마다 변경될 수 있습니다. 이후 페이지에서 다른 여백을 설정하는 경우, 첫 번째 값을 캐싱하지 말고 getMargins()를 다시 읽으십시오.
  • 영역은 텍스트를 자동으로 재배치하지 않습니다. 쓰기 금지 영역은 자동 텍스트 줄바꿈 경계가 아니라 반드시 준수해야 하는 제약입니다. 이 레시피는 명시적인 충돌 검사를 보여줍니다. 자유 위치 지정 방식으로 작성할 경우, 엔진은 콘텐츠를 영역 밖으로 스스로 이동시키지 않습니다.

여기서 사용하는 모든 메서드는 속성 읽기입니다. 상수 시간에 실행되고, 할당이 없으며, 입력이나 출력도 없습니다. 이 예제는 1500 ms / 96 MB 예산 안에서 작은 다중 페이지 문서를 충분히 여유 있게 만듭니다. 재현성 프로파일은 시맨틱입니다. 생성된 PDF가 트레일러 /ID와 생성 메타데이터를 포함하기 때문입니다. 이 예제에서 중요한 것은 관찰 가능한 지오메트리 결정이므로, 하네스는 바이트 해시가 아니라 구조적 추상 구문 트리(AST)와 메타데이터 비교로 그 결정을 검증합니다.

  • 데이터 레지던시 및 PII 완화. 해당 사항 없음. 이 레시피는 지오메트리를 읽고 호출자가 제공한 텍스트를 배치하므로 새로운 데이터 경로를 도입하지 않습니다. 배치하는 텍스트에도 다른 곳에서와 동일한 최소화 원칙을 적용하십시오.
  • 안전한 텔레메트리 및 로그 스크러빙. 이 예제는 지오메트리 수치와 고정된 진행 메시지를 출력합니다. 문서 텍스트를 로깅하지 않습니다.
  • 위협 모델. 해당 사항 없음. 조회 기능은 읽기 전용이며 외부 입력을 파싱하지 않습니다. 신뢰 경계가 아닙니다.
  • FIPS 모드 동작. 해당 사항 없음. 암호화 연산이 실행되지 않습니다.
설명사양조항reference_id
페이지 객체는 경계 박스(MediaBox)를 통해 범위를 정의합니다.ISO 32000-2§7.7.3.3
페이지 사각형은 콘텐츠 경계입니다.ISO 32000-2§7.7.3.3
위치는 사용자 공간에서 측정되며, 1 단위는 1/72 인치입니다.ISO 32000-2§8.3.2

이 레시피는 인용된 ISO 32000-2 페이지 박스 및 사용자 공간 조항에서 정의한 지오메트리를 읽습니다. 전면적인 ISO 32000-2 적합성을 주장하지는 않습니다. 인용된 조항은 레이아웃 조회 기능이 의존하는 조항입니다.