การแบ่งหน้า HTML ยาวให้ไหลข้ามหลายหน้า
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”ใช้การแบ่งหน้าอัตโนมัติเพื่อให้เนื้อหายาวไหลข้ามหน้าต่างๆ เพิ่มโครงร่างเพื่อให้ผู้อ่านข้ามไปยังส่วนต่างๆ ได้ สูตรนี้อ้างอิงจาก examples/12-bookmarks-and-toc.php
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”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) ที่ถูกเก็บไว้
ขอบเขต API
หัวข้อที่มีชื่อว่า “ขอบเขต API”setAutoPageBreak(bool $enabled, float $margin = 20): static—NextPDF\Core\Concerns\HasPagesbookmark(string $title, int $level = 0, float $y = -1): static—NextPDF\Core\Concerns\HasNavigationmultiCell(...)/writeHtml(string $html): static—NextPDF\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) | 3 | Verified | src/Html/Fragmentation/, tests/Unit/Html/PagedMedia/ |
CSS Table (css_tables_3) | 3 | Verified | src/Html/Table/ + golden PDFs |
CSS Cascading and Inheritance (css_cascade_3) | 3 | Verified | src/Html/Cascade/ |
@page เป็นตัวเลือกหน้าที่มีชื่อ (named-page selector) ซึ่งอยู่ในโมดูล CSS Paged Media ก่อนพึ่งพาตัวเลือกเหล่านี้ ให้ตรวจสอบเมทริกซ์เพื่อดูระดับปัจจุบันของโมดูลนั้น
ข้อจำกัดของการสตรีมแบบรอบเดียว (ADR-001)
หัวข้อที่มีชื่อว่า “ข้อจำกัดของการสตรีมแบบรอบเดียว (ADR-001)”เอนจินจะปล่อยการแบ่งหน้าขณะสตรีมไหลออกมา เนื่องจากไม่มีต้นไม้ที่ถูกเก็บไว้เพื่อจัดการการไหลใหม่ การตัดสินใจแบ่งแต่ละครั้งจึงเป็นที่สิ้นสุด เนื้อหาบางอย่าง เช่น การอ้างอิงไขว้ จำเป็นต้องใช้เลขหน้าสุดท้ายหลังจากจัดเค้าโครงแล้ว เนื้อหาประเภทนี้จึงมีข้อจำกัด และต้องเขียนโดยคำนึงถึงข้อจำกัดดังกล่าว
สัญญาของเลเยอร์ (ADR-010)
หัวข้อที่มีชื่อว่า “สัญญาของเลเยอร์ (ADR-010)”การแบ่งหน้าเป็นหน้าที่ของตัวควบคุมการแบ่งหน้า (page-break controller) ไม่ใช่ของตัวแจง (parser) ตัวแจงไม่ได้ปล่อยตัวดำเนินการเปลี่ยนหน้าแบบดิบ แต่ร้องขอการแบ่งผ่านสัญญาของตัวควบคุม
งบประมาณหน่วยความจำสำหรับเอกสารขนาดใหญ่
หัวข้อที่มีชื่อว่า “งบประมาณหน่วยความจำสำหรับเอกสารขนาดใหญ่”แบบจำลองการสตรีมจะเก็บเฉพาะบัฟเฟอร์ของหน้าปัจจุบันและรายการโครงร่างแบบแบน ไม่ได้เก็บทุกหน้าพร้อมกัน เอกสารที่ยาวมากยังคงอยู่ภายในเพดานของ ADR-020 เพราะเอนจินจะล้าง (flush) หน้าที่เสร็จสมบูรณ์แล้วออกไป ตารางและคอนเทนเนอร์แบบ flex ยังคงเป็นไปตามขอบเขต 5,000 โหนดต่อบริบท
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”เอกสารที่เป็นภัยไม่สามารถบังคับให้ใช้หน่วยความจำได้อย่างไม่จำกัด ขีดจำกัดของอิลิเมนต์และการซ้อน (ADR-001) รวมถึงงบประมาณโหนดต่อบริบท (ADR-020) จะกำหนดขอบเขตของงาน ตรวจสอบความถูกต้องของความยาวและโครงสร้างของเนื้อหายาวที่ผู้ใช้ป้อนเข้ามา เอนจินจะเรนเดอร์ชื่อโครงร่างที่ผู้โจมตีควบคุมเป็นข้อความ และไม่ตีความชื่อนั้น
ความสอดคล้องตามข้อกำหนด
หัวข้อที่มีชื่อว่า “ความสอดคล้องตามข้อกำหนด”| ข้อความระบุ | ข้อกำหนด | อนุประโยค | รหัสอ้างอิง (reference_id) |
|---|---|---|---|
| รายการโครงร่างแต่ละรายการอาจเชื่อมโยงกับปลายทาง เพื่อให้ผู้ใช้ข้ามไปยังรายการนั้นได้โดยตรง | ISO 32000-2 | iso32000_2_sec12#x1.x5.p4 | |
| รายการ Dest ของรายการโครงร่างจะระบุชื่อปลายทางที่แสดงเมื่อเปิดใช้งานรายการนั้น | ISO 32000-2 | iso32000_2_sec12#x1.x11.p30 |
สูตรนี้แสดงให้เห็นว่า NextPDF จัดให้เนื้อหายาวไหลและสร้างโครงร่างอย่างไร เมทริกซ์การรองรับจัดระดับ CSS Fragmentation เป็น Verified
บริบทเชิงพาณิชย์
หัวข้อที่มีชื่อว่า “บริบทเชิงพาณิชย์”ไม่มี