Aller au contenu

Paginer un long contenu HTML sur plusieurs pages

Répartis un long contenu sur plusieurs pages, avec des sauts de page automatiques. Ajoute un plan pour qu’un lecteur puisse passer directement d’une section à l’autre. Ce recipe reprend examples/12-bookmarks-and-toc.php.

Fenêtre de terminal
composer require nextpdf/core:^3

Cette contrainte cible le package nextpdf/core. L’exemple s’exécute avec PHP 8.4.

setAutoPageBreak(true, $margin) indique au moteur de commencer une nouvelle page dès que le contenu dépasserait le seuil de la marge inférieure. Le moteur fragmente à ce seuil le texte long produit avec multiCell() ou writeHtml(). Le module CSS Fragmentation (css_break_3) est noté Verified dans la matrice de prise en charge et sert de socle au comportement de saut pour le pipeline HTML.

bookmark($title, $level) ajoute un élément de plan qui pointe vers la position courante. Un élément de plan PDF associe une destination, ce qui permet à l’utilisateur d’atteindre directement une page (ISO 32000-2). Le moteur écrit cette destination dans l’entrée Dest de l’élément (ISO 32000-2). L’argument level détermine l’imbrication des éléments dans une table des matières hiérarchique, dans la barre latérale du lecteur.

Le pipeline reste en passe unique (ADR-001). La pagination est décidée au fil de l’émission du flux, et non par un arbre de mise en page gardé en mémoire.

  • setAutoPageBreak(bool $enabled, float $margin = 20): staticNextPDF\Core\Concerns\HasPages.
  • bookmark(string $title, int $level = 0, float $y = -1): staticNextPDF\Core\Concerns\HasNavigation.
  • multiCell(...) / writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutput.

Le tableau PHPDoc complet est généré à partir du code source.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setAutoPageBreak(true, margin: 25);
$doc->addPage();
$doc->bookmark('Section 1', level: 0);
$doc->setFont('helvetica', '', 11);
for ($i = 1; $i <= 80; $i++) {
$doc->multiCell(0, 7, "Paragraph {$i} of a long flowing document.");
}
$doc->save(__DIR__ . '/out.pdf');

Cet exemple est autonome et exécutable par le harnais. Il construit un document à plusieurs chapitres avec un plan imbriqué et des sauts de page automatiques, puis reprend examples/12-bookmarks-and-toc.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Bookmarks and Navigation');
$doc->setPrintHeader(false);
$doc->setPrintFooter(false);
$doc->setAutoPageBreak(true, margin: 25);
$chapters = [
'Chapter 1: Introduction' => ['What is NextPDF?', 'Key Features'],
'Chapter 2: Getting Started' => ['Installation', 'Your First PDF'],
'Chapter 3: Advanced Topics' => ['Worker-safe Architecture', 'Streaming Output'],
];
$body = 'NextPDF is a modern PDF 2.0 library for PHP. This paragraph is '
. 'repeated so the content overflows the page and the engine inserts '
. 'an automatic page break at the bottom-margin threshold.';
foreach ($chapters as $chapter => $sections) {
$doc->addPage();
$doc->bookmark($chapter, level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, $chapter, newLine: true);
$doc->ln(3);
foreach ($sections as $section) {
$doc->bookmark($section, level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, $section, newLine: true);
$doc->setFont('helvetica', '', 11);
for ($i = 0; $i < 12; $i++) {
$doc->multiCell(0, 7, $body);
}
$doc->ln(4);
}
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/paginate-long-html.pdf');
echo "Wrote paginate-long-html.pdf\n";

Sortie standard attendue :

Wrote paginate-long-html.pdf
  • Désactiver puis oublier de réactiver. Quand le saut de page automatique est désactivé, le moteur rogne le contenu qui dépasse la marge inférieure au lieu de le répartir. Réactive-le avant d’émettre un long contenu.
  • Contenu insécable. Un bloc unique plus haut que la hauteur utile de la page peut lever UnsplittableContentException. Une ligne de tableau très haute ou une grande image peut en être la cause. Découpe le contenu source.
  • Place le signet avant le contenu. Appelle bookmark() à la position vers laquelle tu veux que la destination pointe. Place-le juste avant le titre que tu écris ensuite, sur la page voulue.
  • L’en-tête et le pied de page réservent de l’espace. Un en-tête ou un pied de page d’impression réduit la hauteur utile du contenu, et le seuil de saut en tient compte. En désactivant les deux, comme dans l’exemple, tu disposes de toute la hauteur du corps.
  • Imbrication du plan. level est la profondeur d’imbrication. Un enfant au level: 1 doit suivre un parent au level: 0. Sinon, le lecteur aplatit l’arbre du plan.

Le moteur décide de la pagination pendant l’unique passe d’émission. Le coût est linéaire en fonction de la longueur du contenu, O(n). Le budget est wall_ms: 2000, peak_mb: 96. Le temps mural est légèrement plus élevé que pour les recipes à page unique, à cause de l’assemblage de la table xref multipage et du plan. Le modèle de streaming maintient une mémoire bornée, et le plan reste une petite liste plate.

Extrait de la matrice de prise en charge CSS (lignes Verified uniquement)

Section intitulée « Extrait de la matrice de prise en charge CSS (lignes Verified uniquement) »

Seules les lignes Verified de la matrice de prise en charge CSS, dont l’exactitude a été auditée, sont reproduites.

Module W3CNiveauStatutPreuve
CSS Fragmentation (css_break_3)3Verifiedsrc/Html/Fragmentation/, tests/Unit/Html/PagedMedia/
CSS Table (css_tables_3)3Verifiedsrc/Html/Table/ + PDF de référence
CSS Cascading and Inheritance (css_cascade_3)3Verifiedsrc/Html/Cascade/

@page désigne des sélecteurs de pages nommées qui font partie de CSS Paged Media. Consulte la matrice pour connaître le niveau actuel de ce module avant de t’y fier.

Contraintes du streaming en passe unique (ADR-001)

Section intitulée « Contraintes du streaming en passe unique (ADR-001) »

Le moteur émet les sauts de page au fil du flux. Il n’existe aucun arbre conservé en mémoire pour redistribuer le contenu : une décision de saut est donc définitive une fois prise. Certains contenus ont besoin de leur numéro de page final après la mise en page, par exemple une référence croisée. Ce type de contenu est contraint ; rédige-le donc en gardant cette limitation à l’esprit.

La pagination appartient au contrôleur de saut de page, pas à l’analyseur. L’analyseur n’émet pas d’opérateurs de transition de page bruts ; il demande un saut via le contrat du contrôleur.

Le modèle de streaming conserve le tampon de la page courante et la liste plate du plan, mais pas toutes les pages à la fois. Un document très long reste dans la limite de l’ADR-020 parce que le moteur vide les pages terminées. Les tableaux et les conteneurs flex respectent toujours la limite de 5,000 nœuds par contexte.

Un document hostile ne peut pas forcer une consommation mémoire illimitée. Les limites d’éléments et d’imbrication (ADR-001), ainsi que le budget de nœuds par contexte (ADR-020), bornent le travail. Valide la longueur et la structure d’un long contenu fourni par l’utilisateur. Le moteur rend comme du texte un titre de plan contrôlé par un attaquant, sans jamais l’interpréter.

ÉnoncéSpécificationClausereference_id
Chaque élément de plan peut être associé à une destination afin que l’utilisateur l’atteigne directement.ISO 32000-2iso32000_2_sec12#x1.x5.p4
L’entrée Dest d’un élément de plan nomme la destination affichée quand l’élément est activé.ISO 32000-2iso32000_2_sec12#x1.x11.p30

Ce recipe montre comment NextPDF répartit un long contenu et construit un plan. CSS Fragmentation est noté Verified dans la matrice de prise en charge.

Sans objet.