Inspecter les boîtes de mise en page avant de placer du contenu
Lis la géométrie de mise en page d’un document en temps réel avant de placer du contenu. Cette recette interroge la boîte de page, les marges, les éventuelles zones de non-écriture et la position courante du curseur. Ta logique de placement s’appuie alors sur des valeurs réelles plutôt que sur des coordonnées devinées. Cette recette s’appuie sur examples/34-inspect-layout-boxes.php et son harnais de test tests/Cookbook/Php/InspectLayoutBoxesRecipeTest.php.
Installation
Section intitulée « Installation »composer require nextpdf/core:^3Tu n’as besoin d’aucun package Pro ou Enterprise. La surface d’interrogation de mise en page fait partie du cœur et fonctionne avec PHP 8.1 à 8.4.
Aperçu conceptuel
Section intitulée « Aperçu conceptuel »Une page PDF possède des boîtes de délimitation. Le minimum requis est la MediaBox, le rectangle qui définit l’étendue de la page (ISO 32000-2 §7.7.3.3). Le contenu est positionné dans l’espace utilisateur. Par défaut, cet espace a son origine dans le coin inférieur gauche, et une unité vaut 1/72 de pouce (§8.3.2). NextPDF présente une vue pratique repérée depuis le coin supérieur gauche et expose la géométrie via des méthodes d’interrogation en lecture seule :
getPageWidth()/getPageHeight()— les dimensions de la boîte de page.getMargins()— l’objet valeurMarginen vigueur (haut/droite/bas/gauche).getPageRegions()— les zones de non-écriture déclarées (PageRegion[]). Chacune est un rectangle immuable dans lequel le placement de contenu est interdit.getX()/getY()— le curseur en temps réel dans l’espace auteur.
Ce sont des lectures idempotentes. Elles n’émettent pas de contenu, n’avancent pas le curseur et ne modifient pas l’état. Utilise-les pour calculer l’espace vertical restant, puis décider s’il faut continuer à écrire ou appeler addPage(). Tu peux aussi les utiliser pour positionner un bloc par rapport à une zone de non-écriture, plutôt qu’à partir d’un décalage codé en dur.
Surface de l’API
Section intitulée « Surface de l’API »La surface de l’API est générée à partir du PHPDoc. Les principaux points d’entrée se trouvent dans les traits \NextPDF\Core\Concerns\HasPages et HasLayout :
Document::getPageWidth(): float/Document::getPageHeight(): floatDocument::getMargins(): \NextPDF\ValueObjects\MarginDocument::getPageRegions(): array(list<\NextPDF\Layout\PageRegion>)Document::addPageRegion(float $x, float $y, float $w, float $h): staticDocument::getX(): float/Document::getY(): float
Exemple de code — Démarrage rapide
Section intitulée « Exemple de code — Démarrage rapide »<?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);Exemple de code — Production
Section intitulée « Exemple de code — Production »Voici le programme autonome, exécutable par le harnais de test. Il reflète examples/34-inspect-layout-boxes.php. Il lit la géométrie de la page, déclare une zone de non-écriture en pied de page, puis prend une décision fondée sur les données pour chaque bloc. Si le bloc suivant devait entrer en collision avec une zone ou dépasser la marge inférieure, il ajoute une page au lieu de surimprimer.
<?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);Sortie STDOUT attendue. Les nombres de la boîte de page reflètent la taille de page A4 par défaut, et le curseur démarre à l’origine du contenu en haut à gauche :
Page box: 595.28 x 841.89 ptCursor start: (<x>, <y>)Pages used: 2 (page breaks decided from layout geometry)Cas limites & pièges
Section intitulée « Cas limites & pièges »- Lire avant que la page existe.
getPageWidth()etgetPageHeight()reflètent la page courante : appelle-les donc aprèsaddPage(). Avant la première page, elles renvoient la géométrie de la taille de page par défaut, pas celle de la page que tu n’as pas encore ajoutée. - Les zones sont des rectangles en espace auteur. Un
PageRegionyest la distance dans l’espace auteur depuis le haut à gauche, cohérente avecgetY(). Ne la mélange pas avec des coordonnées PDF brutes en bas à gauche. - Les lectures sont sans effet de bord. Aucune des méthodes d’interrogation n’avance le curseur ni n’émet de contenu. Les appeler dans une boucle serrée est sûr et coûte peu.
- Les marges peuvent changer d’une page à l’autre. Si une page ultérieure définit des marges différentes, relis
getMargins()au lieu de mettre en cache la première valeur. - Les zones ne réagencent pas le texte automatiquement. Une zone de non-écriture est une contrainte que tu dois respecter, pas une limite de retour à la ligne automatique du texte. La recette montre la vérification explicite des collisions. Pour le contenu positionné librement, le moteur ne déplace jamais de lui-même du contenu hors d’une zone.
Performance
Section intitulée « Performance »Chaque méthode ici est une lecture de propriété. Elle s’exécute en temps constant, sans allocation ni entrée/sortie. L’exemple construit un petit document multipage bien en deçà du budget de 1500 ms / 96 Mo. Le profil de reproductibilité est sémantique, car le PDF généré porte le /ID du trailer et les métadonnées de création. Ce qui compte dans l’exemple, ce sont ses décisions de géométrie observables : le harnais valide donc ces décisions au moyen d’un arbre de syntaxe abstraite (AST) structurel et d’une comparaison de métadonnées plutôt que d’un hachage d’octets.
Notes de sécurité
Section intitulée « Notes de sécurité »- Résidence des données & atténuations PII. Sans objet. La recette lit la géométrie et met en page du texte fourni par l’appelant ; elle n’introduit donc aucun nouveau chemin de données. Applique au texte que tu places les mêmes principes de minimisation que partout ailleurs.
- Télémétrie sûre & nettoyage des journaux. L’exemple affiche des nombres de géométrie et une ligne de progression fixe. Il ne journalise pas le texte du document.
- Modèle de menace. Sans objet. La surface d’interrogation est en lecture seule et n’analyse aucune entrée externe. Ce n’est pas une frontière de confiance.
- Comportement en mode FIPS. Sans objet. Aucune opération cryptographique ne s’exécute.
Conformité
Section intitulée « Conformité »| Déclaration | Spécification | Clause | reference_id |
|---|---|---|---|
| Un objet page définit son étendue au moyen de boîtes de délimitation (MediaBox). | ISO 32000-2 | §7.7.3.3 | |
| Le rectangle de page est la frontière du contenu. | ISO 32000-2 | §7.7.3.3 | |
| Les positions sont mesurées dans l’espace utilisateur ; une unité vaut 1/72 de pouce. | ISO 32000-2 | §8.3.2 |
Cette recette lit la géométrie que définissent les clauses citées d’ISO 32000-2 sur la boîte de page et l’espace utilisateur. Elle ne revendique pas une conformité générale à ISO 32000-2. Les clauses citées sont celles sur lesquelles s’appuie la surface d’interrogation de mise en page.