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

ไปป์ไลน์การเรนเดอร์ HTML

เมื่อเรียก writeHtml() ระบบจะประมวลผล HyperText Markup Language (HTML) แบบเดินหน้าเพียงรอบเดียว ได้แก่การแปลงอินพุตเป็นโทเคน การแก้ค่า @page และสไตล์ การจัดเค้าโครงเนื้อหา และการวาดตัวดำเนินการ Portable Document Format (PDF) ไปป์ไลน์ไม่เก็บทรีของอิลิเมนต์ไว้ข้ามขั้นตอนต่าง ๆ

Terminal window
composer require nextpdf/core:^3

ไปป์ไลน์การเรนเดอร์ HTML แปลง HTML+CSS ซึ่งหมายถึง HTML รวมกับ Cascading Style Sheets (CSS) ให้เป็นตัวดำเนินการของคอนเทนต์สตรีม PDF ด้วยการประมวลผลแบบเดินหน้าเพียงรอบเดียว ไปป์ไลน์ไม่สร้างทรีเอกสารแบบคงอยู่ ขั้นตอนด้านล่างสะท้อน HtmlParser::parse() บน main ในปัจจุบัน

ขั้นที่ 1 — ทำให้ปลอดภัยและทำให้เป็นมาตรฐาน HtmlParser::parse() ปฏิเสธอินพุตที่มีขนาดเกิน 10 MB ตัดอักขระควบคุมออก และทำให้การขึ้นบรรทัดใหม่เป็นมาตรฐาน โดยทั้ง CRLF และ CR เดี่ยวจะกลายเป็น LF ซึ่งตรงกับการทำให้การขึ้นบรรทัดใหม่ของ HTML เป็นมาตรฐานในต้นฉบับ จากนั้นรีเซ็ตทุกฟิลด์ของอินสแตนซ์ เพื่อไม่ให้สถานะจากการเรียกครั้งก่อนส่งต่อมาได้

ขั้นที่ 2 — แยก @page และบล็อกสไตล์ ตัวแยกวิเคราะห์จะแยกบล็อก <style> ออกมาก่อน จากนั้นนำกฎ @page ที่พบมาใช้กำหนดรูปทรงของหน้าใหม่ ขั้นตอนนี้เกิดขึ้นก่อนการประมวลผลโทเคนใด ๆ เพราะขนาดหน้าส่งผลต่อการตัดสินใจจัดเค้าโครงทุกครั้งหลังจากนั้น

ขั้นที่ 3 — แปลงเป็นโทเคน HtmlTokenizer::cleanHtml() ทำให้ช่องว่างเป็นมาตรฐานพร้อมรักษาเนื้อหา <pre> ไว้ จากนั้น tokenize() จะสร้าง list<HtmlToken> แบบแบนราบ ผลลัพธ์นี้เป็นรายการโทเคน ไม่ใช่กราฟของโหนด ไปป์ไลน์จะทิ้งโทเคนข้อความที่มีเฉพาะช่องว่างทันที HtmlChildScanner::scan() สร้างแมปดัชนี (จำนวนโหนดลูก จำนวนแท็ก ความว่างเปล่า) บนรายการแบบแบนราบ ดังนั้นตัวเลือกเชิงโครงสร้างจึงไม่ต้องใช้ทรี

ขั้นที่ 4 — การพรีสแกน :has() ที่เป็นตัวเลือก เมื่อเปิดใช้คุณสมบัติทดลอง css.has CssResolver::resolveHasSelectors() จะพรีสแกนรายการโทเคนแบบมีขอบเขตจำกัดหนึ่งครั้งเพื่อแก้ค่าตัวเลือกเชิงสัมพันธ์ ขั้นตอนที่มีเอกสารกำกับและมีขอบเขตจำกัดนี้เป็นข้อยกเว้นของกฎการประมวลผลรอบเดียว

ขั้นที่ 5 — ประมวลผลโทเคน (สไตล์ เค้าโครง การวาด) HtmlParser::processTokens() เดินผ่านรายการโทเคนหนึ่งครั้ง สำหรับแต่ละอิลิเมนต์ จะแก้ค่า cascade (ตัวประยุกต์ใช้ของ Layer 1 เขียน HtmlStyleState) คำนวณรูปทรง (การจัดเค้าโครงของ Layer 3) และส่งออกตัวดำเนินการ PDF (การวาดของ Layer 4) การสืบทอดสไตล์ใช้สแตก HtmlStyleState แบบ push-and-pop เคอร์เซอร์ (x, y ระยะขอบ ออฟเซ็ตของสตรีม) ถูกส่งต่อระหว่างตัวจัดการต่าง ๆ ผ่านสแนปช็อตของ HtmlBlockCursor ในแต่ละขั้น

ขั้นที่ 6 — คืนค่าผลลัพธ์ parse() คืนค่า HtmlRenderResult ที่เปลี่ยนแปลงไม่ได้ พร้อมคอนเทนต์สตรีมที่ส่งออก ตำแหน่งเคอร์เซอร์สุดท้าย และคีย์ฟอนต์ที่ใช้ ตัวเรียก (writeHtml()) ส่งเคอร์เซอร์กลับไปยังกรอบพิกัดของหน้า

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

สัญลักษณ์ตำแหน่งขั้น
Document::writeHtml(string $html): staticsrc/Core/Concerns/HasTextOutput.phpจุดเข้าใช้สาธารณะ
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpประสานงานทุกขั้นตอน
HtmlTokenizer::cleanHtml() / tokenize()src/Html/HtmlTokenizer.phpขั้นที่ 3
HtmlChildScanner::scan()src/Html/HtmlChildScanner.phpแมปดัชนีของขั้นที่ 3
CssResolver::resolveHasSelectors()src/Html/CssResolver.phpขั้นที่ 4 (มีการควบคุมการเปิดใช้)
HtmlRenderResult (stream, endX, endY, usedFontKeys)src/Html/HtmlRenderResult.phpขั้นที่ 6

ตัวอย่างนี้มาจาก examples/08-html-basic.php ในที่เก็บโค้ด

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$doc->writeHtml('<h1 style="color:#1E3A8A;">HTML Rendering</h1><p>One pass.</p>');
$doc->save(__DIR__ . '/output/08-html-basic.pdf');

เรนเดอร์รายงานที่จัดสไตล์ไว้และมีบล็อก <style> ฝังอยู่ ไปป์ไลน์จะแยกและนำบล็อกสไตล์มาใช้ก่อนการประมวลผลโทเคนใด ๆ

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\HtmlParsingException;
function renderInvoice(string $bodyHtml, string $out): void
{
$doc = Document::createStandalone();
$doc->setTitle('Invoice');
$doc->addPage();
$html = '<style>@page { margin: 20mm; } '
. 'h1 { color: #1E3A8A; } '
. 'table { width: 100%; }</style>'
. $bodyHtml;
try {
$doc->writeHtml($html);
} catch (HtmlParsingException $e) {
// Sanitize/cap failures surface here. Do not retry.
throw $e;
}
$doc->save($out);
}
  • @page ถูกอ่านก่อนโทเคน กฎ @page ที่อยู่หลังเนื้อหายังคงมีผล เพราะการแยกสไตล์เกิดขึ้นก่อนการแปลงเป็นโทเคน รูปทรงของหน้าจะถูกกำหนดตายตัวก่อนขั้นที่ 5
  • ช่องว่างใน <pre> จะถูกรักษาไว้ cleanHtml() ปกป้องเนื้อหา <pre> ส่วนช่องว่างในตำแหน่งอื่น ๆ ไปป์ไลน์จะยุบรวมเข้าด้วยกัน
  • :has() มีการควบคุมการเปิดใช้ หากไม่ได้เปิดใช้คุณสมบัติทดลอง css.has ขั้นที่ 4 จะไม่ทำงานและตัวเลือก :has() จะไม่จับคู่
  • บัฟเฟอร์สตรีมเดียว ไปป์ไลน์เขียนลงบัฟเฟอร์สตริงเพียงตัวเดียว และไม่เคยย้ายเนื้อหาที่เขียนไปแล้ว จึงไม่มีการจัดเค้าโครงใหม่
  • ขีดจำกัดมีผลระหว่างการประมวลผล ขีดจำกัดของอิลิเมนต์และการซ้อนจะส่งข้อยกเว้นระหว่างขั้นที่ 5 ไม่ใช่ก่อนหน้านั้น เอกสารจึงอาจล้มเหลวกลางคันได้

ไปป์ไลน์เดินผ่านด้วยความซับซ้อน O(จำนวนโทเคน) การกำหนดขนาดคอลัมน์ของตารางเพิ่มการสแกนแถวแบบมีขอบเขตจำกัดต่อตาราง (ขั้นที่ 5 TableParser) เมื่อเปิดใช้ การพรีสแกน :has() จะเพิ่มการประมวลผลรายการโทเคนแบบมีขอบเขตจำกัดหนึ่งรอบ (ขั้นที่ 4) หน่วยความจำของสแตกสไตล์เป็น O(ความลึกของการซ้อน) ไม่ใช่ O(จำนวนอิลิเมนต์) โปรดดู ข้อจำกัดของสตรีมมิง เกณฑ์มาตรฐานประสิทธิภาพของไปป์ไลน์การเรนเดอร์ HTML ป้องกันการถดถอยด้วยเกตที่ 5% (งานที่ผสานแล้ว PR #564) performance_budget ต่อหน้า (wall_ms: 1500, peak_mb: 64) คือเพดานการทำงาน

ขั้นที่ 1 คือขอบเขตความปลอดภัยแรก ได้แก่ขีดจำกัดอินพุต 10 MB การตัดอักขระควบคุมออก และการทำให้การขึ้นบรรทัดใหม่เป็นมาตรฐาน ซึ่งทั้งหมดทำงานก่อนการแปลงเป็นโทเคน ระหว่างขั้นที่ 5 DefaultHtmlSecurityPolicy ควบคุมแท็ก แอตทริบิวต์ คุณสมบัติ CSS และสคีม URL ที่อนุญาต โปรดดู โมเดลความปลอดภัยของโมดูล HTML ประกอบ

การทำให้การขึ้นบรรทัดใหม่เป็นมาตรฐานเป็นไปตามการจัดการการขึ้นบรรทัดใหม่ของมาตรฐาน HTML โดย CRLF และ CR เดี่ยวจะกลายเป็น LF ความสอดคล้องของ CSS แบบรายคุณสมบัติมีเอกสารกำกับใน เมทริกซ์การรองรับ CSS และพฤติกรรมการ cascade มีเอกสารกำกับใน css-resolver หน้านี้ไม่ระบุการรองรับแบบรายคุณสมบัติซ้ำ

ความสามารถระดับองค์กร Premium ขยายการรองรับ CSS ให้กว้างขึ้นบนไปป์ไลน์เดียวกัน ลำดับหกขั้นไม่เปลี่ยนแปลงระหว่างรุ่นต่าง ๆ โปรดดู เมทริกซ์การรองรับ CSS ประกอบ