ข้ามไปยังเนื้อหา

การแบ่งหน้า HTML ยาวให้ไหลข้ามหลายหน้า

ใช้การแบ่งหน้าอัตโนมัติเพื่อให้เนื้อหายาวไหลข้ามหน้าต่างๆ เพิ่มโครงร่างเพื่อให้ผู้อ่านข้ามไปยังส่วนต่างๆ ได้ สูตรนี้อ้างอิงจาก examples/12-bookmarks-and-toc.php

Terminal window
composer require nextpdf/core:^3

ใช้ข้อกำหนดเวอร์ชันนี้กับแพ็กเกจ nextpdf/core ตัวอย่างนี้ทำงานบน PHP 8.4

setAutoPageBreak(true, $margin) สั่งให้เอนจินเริ่มหน้าใหม่ก่อนที่เนื้อหาจะเกินเกณฑ์ขอบล่างของหน้า เอนจินจะแบ่งข้อความยาวที่เขียนผ่าน multiCell() หรือ writeHtml() ณ ขอบเขตนั้น โมดูล Cascading Style Sheets (CSS) Fragmentation (css_break_3) ได้รับการจัดระดับเป็น Verified ในเมทริกซ์การรองรับ โมดูลนี้รองรับพฤติกรรมการแบ่ง (break) ในไปป์ไลน์ Hypertext Markup Language (HTML)

bookmark($title, $level) เพิ่มรายการโครงร่างสำหรับตำแหน่งปัจจุบัน รายการโครงร่างของ Portable Document Format (PDF) จะเชื่อมโยงกับปลายทาง เพื่อให้ผู้อ่านข้ามไปยังหน้าได้โดยตรง (ISO 32000-2) เอนจินจะเขียนปลายทางนั้นเป็นรายการ Dest ของรายการโครงร่าง (ISO 32000-2) ใช้อาร์กิวเมนต์ level เพื่อซ้อนรายการเป็นสารบัญแบบลำดับชั้นในแถบด้านข้างของโปรแกรมอ่าน

ไปป์ไลน์ยังคงเป็นแบบรอบเดียว (single-pass) (ADR-001) เอนจินจะตัดสินใจแบ่งหน้าขณะปล่อยสตรีมออกมา โดยไม่มีต้นไม้เค้าโครง (layout tree) ที่ถูกเก็บไว้

  • 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

ตาราง PHPDoc ฉบับเต็มถูกสร้างขึ้นจากซอร์สโค้ด

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

ตัวอย่างแบบครบในตัวนี้ทำงานได้ในชุดทดสอบ (harness) ตัวอย่างนี้สร้างเอกสารหลายบทพร้อมโครงร่างแบบซ้อนและการแบ่งหน้าอัตโนมัติ และสอดคล้องกับ 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";

เอาต์พุตมาตรฐาน (STDOUT) ที่คาดหวัง:

Wrote paginate-long-html.pdf
  • ปิดแล้วลืม เมื่อปิดการแบ่งหน้าอัตโนมัติ เอนจินจะตัดเนื้อหาที่เลยขอบล่างทิ้งแทนที่จะปล่อยให้ไหลต่อ เปิดใช้งานอีกครั้งก่อนเนื้อหายาว
  • เนื้อหาที่แบ่งไม่ได้ บล็อกเดียวที่สูงกว่าความสูงของหน้าที่ใช้ได้อาจทำให้เกิด UnsplittableContentException แถวตารางที่สูงมากหรือรูปภาพขนาดใหญ่อาจทำให้เกิดกรณีนี้ แบ่งเนื้อหาต้นทางออกเป็นส่วนย่อย
  • ทำบุ๊กมาร์กก่อนเนื้อหา เรียก bookmark() ที่ตำแหน่งซึ่งต้องการให้ปลายทางชี้ไป วางไว้ทันทีก่อนหัวข้อถัดไปบนหน้าที่ต้องการ
  • ส่วนหัวและส่วนท้ายจองพื้นที่ไว้ ส่วนหัวหรือส่วนท้ายสำหรับพิมพ์จะลดความสูงของเนื้อหาที่ใช้ได้ลง และเกณฑ์การแบ่งหน้าจะคำนึงถึงพื้นที่นี้ การปิดทั้งสองอย่างตามตัวอย่างนี้จะให้ความสูงของเนื้อหาเต็มพื้นที่
  • การซ้อนโครงร่าง level คือความลึกของการซ้อน รายการลูกที่ level: 1 ต้องตามหลังรายการแม่ที่ level: 0 มิฉะนั้นโปรแกรมอ่านจะยุบต้นไม้โครงร่างให้แบนราบ

เอนจินจะตัดสินใจแบ่งหน้าระหว่างรอบการปล่อยสตรีมเพียงรอบเดียว ต้นทุนเป็นเชิงเส้นตามความยาวของเนื้อหา O(n) งบประมาณคือ wall_ms: 2000, peak_mb: 96 เวลาที่ใช้จริง (wall time) สูงกว่าสูตรแบบหน้าเดียวเล็กน้อย เพราะตารางอ้างอิงไขว้ (xref) แบบหลายหน้าและการประกอบโครงร่างเพิ่มงานเข้ามา แบบจำลองการสตรีมทำให้ใช้หน่วยความจำในขอบเขตจำกัด และโครงร่างยังคงเป็นรายการแบนขนาดเล็ก

ส่วนตัดตอนจากเมทริกซ์การรองรับ CSS (เฉพาะแถวที่เป็น Verified)

หัวข้อที่มีชื่อว่า “ส่วนตัดตอนจากเมทริกซ์การรองรับ CSS (เฉพาะแถวที่เป็น Verified)”

ตารางนี้แสดงซ้ำเฉพาะแถวที่เป็น Verified จากเมทริกซ์การรองรับ CSSที่ผ่านการตรวจสอบความถูกต้องแล้ว

โมดูล W3Cระดับสถานะหลักฐาน
CSS Fragmentation (css_break_3)3Verifiedsrc/Html/Fragmentation/, tests/Unit/Html/PagedMedia/
CSS Table (css_tables_3)3Verifiedsrc/Html/Table/ + golden PDFs
CSS Cascading and Inheritance (css_cascade_3)3Verifiedsrc/Html/Cascade/

@page เป็นตัวเลือกหน้าที่มีชื่อ (named-page selector) ซึ่งอยู่ในโมดูล CSS Paged Media ก่อนพึ่งพาตัวเลือกเหล่านี้ ให้ตรวจสอบเมทริกซ์เพื่อดูระดับปัจจุบันของโมดูลนั้น

เอนจินจะปล่อยการแบ่งหน้าขณะสตรีมไหลออกมา เนื่องจากไม่มีต้นไม้ที่ถูกเก็บไว้เพื่อจัดการการไหลใหม่ การตัดสินใจแบ่งแต่ละครั้งจึงเป็นที่สิ้นสุด เนื้อหาบางอย่าง เช่น การอ้างอิงไขว้ จำเป็นต้องใช้เลขหน้าสุดท้ายหลังจากจัดเค้าโครงแล้ว เนื้อหาประเภทนี้จึงมีข้อจำกัด และต้องเขียนโดยคำนึงถึงข้อจำกัดดังกล่าว

การแบ่งหน้าเป็นหน้าที่ของตัวควบคุมการแบ่งหน้า (page-break controller) ไม่ใช่ของตัวแจง (parser) ตัวแจงไม่ได้ปล่อยตัวดำเนินการเปลี่ยนหน้าแบบดิบ แต่ร้องขอการแบ่งผ่านสัญญาของตัวควบคุม

แบบจำลองการสตรีมจะเก็บเฉพาะบัฟเฟอร์ของหน้าปัจจุบันและรายการโครงร่างแบบแบน ไม่ได้เก็บทุกหน้าพร้อมกัน เอกสารที่ยาวมากยังคงอยู่ภายในเพดานของ ADR-020 เพราะเอนจินจะล้าง (flush) หน้าที่เสร็จสมบูรณ์แล้วออกไป ตารางและคอนเทนเนอร์แบบ flex ยังคงเป็นไปตามขอบเขต 5,000 โหนดต่อบริบท

เอกสารที่เป็นภัยไม่สามารถบังคับให้ใช้หน่วยความจำได้อย่างไม่จำกัด ขีดจำกัดของอิลิเมนต์และการซ้อน (ADR-001) รวมถึงงบประมาณโหนดต่อบริบท (ADR-020) จะกำหนดขอบเขตของงาน ตรวจสอบความถูกต้องของความยาวและโครงสร้างของเนื้อหายาวที่ผู้ใช้ป้อนเข้ามา เอนจินจะเรนเดอร์ชื่อโครงร่างที่ผู้โจมตีควบคุมเป็นข้อความ และไม่ตีความชื่อนั้น

ข้อความระบุข้อกำหนดอนุประโยครหัสอ้างอิง (reference_id)
รายการโครงร่างแต่ละรายการอาจเชื่อมโยงกับปลายทาง เพื่อให้ผู้ใช้ข้ามไปยังรายการนั้นได้โดยตรงISO 32000-2iso32000_2_sec12#x1.x5.p4
รายการ Dest ของรายการโครงร่างจะระบุชื่อปลายทางที่แสดงเมื่อเปิดใช้งานรายการนั้นISO 32000-2iso32000_2_sec12#x1.x11.p30

สูตรนี้แสดงให้เห็นว่า NextPDF จัดให้เนื้อหายาวไหลและสร้างโครงร่างอย่างไร เมทริกซ์การรองรับจัดระดับ CSS Fragmentation เป็น Verified

ไม่มี