สัญญาเลเยอร์ของเอนจิน HTML (ADR-010)
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”ระบบย่อย Hypertext Markup Language (HTML) แยกการแจงส่วน Cascading Style Sheets (CSS) สถานะสไตล์ เค้าโครง และการ paint ออกเป็นสี่เลเยอร์ ข้อมูลไหลผ่านเลเยอร์เหล่านี้ในทิศทางเดียว Architecture Decision Record 010 (ADR-010) กำหนดขอบเขตและกฎสำหรับการขยาย
การติดตั้ง
หัวข้อที่มีชื่อว่า “การติดตั้ง”composer require nextpdf/core:^3ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”Architecture Decision Record 010 (ADR-010) (“Engine Layer Contracts, Hot Path Ownership, and Extension Rules” รับรองเมื่อ 2026-04-12) กำหนดอย่างเป็นทางการว่าระบบย่อย HTML แบ่งเลเยอร์อย่างไร สัญญาการเรนเดอร์หลักมีสี่เลเยอร์ ได้แก่ การแจงส่วน CSS และ applicator สถานะสไตล์ เค้าโครงและการจัดรูปแบบ และการ paint ADR-010 ยังบันทึกเลเยอร์เสริมอีกสองเลเยอร์ คือ paged media และ measurement harness เลเยอร์เสริมเหล่านี้ครอบแกนสี่เลเยอร์ไว้โดยไม่เปลี่ยนทิศทางการไหลของข้อมูล คำศัพท์มาตรฐานในอภิธานศัพท์สำหรับแกนนี้คือ “HTML pipeline” ซึ่งเป็น pipeline สี่เลเยอร์
ข้อมูลไหลไปในทิศทางเดียว ข้อความ CSS กลายเป็นค่าที่มีชนิดข้อมูลในเลเยอร์ 1 เลเยอร์ 1 เขียนค่าเหล่านั้นลงในฟิลด์ HtmlStyleState ของเลเยอร์ 2 เลเยอร์ 3 อ่านฟิลด์สถานะสไตล์และคำนวณเรขาคณิต เลเยอร์ 4 อ่านสแนปช็อต ComputedStyle ที่ไม่เปลี่ยนแปลงพร้อมเรขาคณิต แล้วส่งออกตัวดำเนินการ Portable Document Format (PDF) ไม่มีเลเยอร์ใดอ่านข้อมูลจากเลเยอร์ถัดไป
การแยกสี่เลเยอร์ไม่ได้เป็นเพียงเอกสารประกอบ ADR-010 บันทึกการ refactor แบบมีขอบเขตสองครั้งที่ใช้ใน v1.2.0 ซึ่งย้ายโค้ดไปยังเลเยอร์ที่ถูกต้อง PageBorderPainter ถูกแยกออกจาก HtmlParser ดังนั้นตัวดำเนินการ paint จึงไม่อยู่ใน orchestrator อีกต่อไป docblock ของคลาส HtmlStyleState ตอนนี้เก็บสัญญาเลเยอร์อย่างเป็นทางการ และระบุว่าแต่ละเลเยอร์เขียนหรืออ่านฟิลด์ใดได้บ้าง
มีขอบเขตหนึ่งที่ระบุไว้อย่างชัดเจน FormattingContextFactory::startTable() ยังคงอ่านคีย์ CSS ดิบห้าคีย์โดยตรง ADR-010 บันทึกกรณีนี้ไว้ว่าเป็นหนี้ทางเทคนิคที่ทราบแล้วและเลื่อนไปไว้สำหรับ TableApplicator ในอนาคต ไม่ใช่สัญญาตามที่ตั้งใจไว้ การบันทึกข้อยกเว้นนี้เป็นส่วนหนึ่งของสัญญา
สี่เลเยอร์หลัก
หัวข้อที่มีชื่อว่า “สี่เลเยอร์หลัก”| เลเยอร์ | ไฟล์ (ตัวแทน) | เขียน | อ่าน | ต้องไม่ |
|---|---|---|---|---|
| 1 — การแจงส่วน CSS & applicator | CssValueParser, CssResolver, HtmlCssApplicator, src/Html/Applicator/* | HtmlStyleState ฟิลด์ CSS | ข้อความ CSS ดิบ | คำนวณเรขาคณิต ส่งออกตัวดำเนินการ |
| 2 — สถานะสไตล์ | HtmlStyleState, State/ComputedStyle, State/LayoutState | — (ถุงค่าแบบเฉื่อย) | — | แจงส่วน CSS ตัดสินใจเค้าโครง ส่งออกตัวดำเนินการ |
| 3 — เค้าโครง & การจัดรูปแบบ | FormattingContextFactory, HtmlBlockHandler, FlexLayoutEngine, TableParser, FloatContext | เรขาคณิตของเคอร์เซอร์ | HtmlStyleState ฟิลด์ | อ่าน $css[...] ดิบ ส่งออกตัวดำเนินการ paint |
| 4 — Paint & การเรนเดอร์ | BorderRenderer, BackgroundImageRenderer, src/Html/Paint/*, src/Html/Gradient/* | สตรีมตัวดำเนินการ PDF | ComputedStyle (ไม่เปลี่ยนแปลง) + เรขาคณิต | คำนวณเรขาคณิต แจงส่วน CSS ตัดสินใจเรื่องการแบ่งหน้า |
สองเลเยอร์เสริม
หัวข้อที่มีชื่อว่า “สองเลเยอร์เสริม”| เลเยอร์ | ไฟล์ (ตัวแทน) | บทบาท |
|---|---|---|
| 5 — Paged media | PageBreakController, PageBorderPainter, PageRule, PageRuleParser, ParserConfigurator | แก้ไขกฎ @page ประเมินข้อจำกัดการแบ่งหน้าและ orphan/widow แล้วมอบหมายการตกแต่งหน้าให้ paint |
| 6 — การวัด & harness | สคริปต์ตัวจำแนกของ Web Platform Tests (WPT), tests/Support/* | จำแนกผลลัพธ์การทดสอบ สร้างสแนปช็อตการถดถอย และให้ตัวช่วยยืนยัน ไม่มีตรรกะการเรนเดอร์ใด ๆ |
พื้นผิว API
หัวข้อที่มีชื่อว่า “พื้นผิว API”สัญญานี้บังคับใช้ผ่านตำแหน่งของคลาสและ docblock ของ HtmlStyleState เทียบกับ src/Html/ สำหรับการตรวจสอบ
| สัญลักษณ์ | เลเยอร์ | บทบาทตามสัญญา |
|---|---|---|
PropertyApplicatorInterface | 1 | อินเทอร์เฟซแบบ strategy เป็นจุดเดียวที่เขียนฟิลด์ CSS แบบ computed |
ParserConfigurator::buildCssApplicator() | 1 (การเดินสาย) | ลงทะเบียน applicator ทุกตัว พร็อพเพอร์ตี CSS ใหม่ลงทะเบียนที่นี่ |
HtmlStyleState | 2 | ถุงแบบสองกลุ่ม docblock ของคลาสระบุเลเยอร์ที่เป็นเจ้าของของแต่ละฟิลด์ |
HtmlStyleState::toComputedStyle() | 2 | สร้าง ComputedStyle ที่ไม่เปลี่ยนแปลงสำหรับเลเยอร์ paint |
FormattingContextFactory::dispatchOpenTag() | 3 | จุดกำหนดเส้นทางเดียวสำหรับพฤติกรรมเค้าโครงใหม่ |
PageBorderPainter::buildStream() | 4 | การตกแต่งหน้า เรียกจากเลเยอร์ 5 โดยไม่ฝังไว้ใน HtmlParser โดยตรง |
ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — เริ่มต้นอย่างรวดเร็ว”คุณไม่จำเป็นต้องแตะเลเยอร์เหล่านี้โดยตรง การไหลแบบสี่เลเยอร์ทำงานอยู่ภายในการเรียกเพียงครั้งเดียว
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml('<p style="color:#1E3A8A;border:1px solid #999;">Layered render.</p>');$doc->save(__DIR__ . '/output/layers.pdf');ตัวอย่างโค้ด — การใช้งานจริง
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — การใช้งานจริง”สัญญานี้สำคัญเมื่อคุณมีส่วนร่วมในการพัฒนา ไม่ใช่เมื่อคุณเรียกใช้ไลบรารี หากต้องการเพิ่มพร็อพเพอร์ตี CSS ให้ใช้จุดขยายของเลเยอร์ 1 โดยสร้าง applicator เพิ่มฟิลด์ HtmlStyleState ที่มีชนิดข้อมูลพร้อม docblock ของเลเยอร์ และลงทะเบียน applicator ใน ParserConfigurator ภาพประกอบด้านล่างแสดงรูปแบบของสัญญา applicator ให้ใช้ src/Html/Applicator/ เป็นแบบจำลองสำหรับคลาสที่เป็นรูปธรรม
<?php
declare(strict_types=1);
// Layer 1 extension contract (see ADR-010 §C "New CSS property").// A new property group ships as a PropertyApplicatorInterface// implementation registered in ParserConfigurator::buildCssApplicator().// It writes a typed HtmlStyleState field and never computes geometry// or emits PDF operators — those belong to Layers 3 and 4.กรณีขอบและข้อควรระวัง
หัวข้อที่มีชื่อว่า “กรณีขอบและข้อควรระวัง”FormattingContextFactory::startTable()อ่าน CSS ดิบ นี่คือข้อยกเว้นเดียวของสัญญาที่บันทึกไว้ และถูกเลื่อนไปยังTableApplicatorในอนาคต อย่าลอกเลียนแบบกรณีนี้- หกเลเยอร์ แกนสี่เลเยอร์ ADR-010 นับรวมเป็นหกเลเยอร์ สัญญาการไหลของข้อมูลคือแกนสี่เลเยอร์ ส่วน paged media และ measurement เป็นเลเยอร์เสริม
HtmlStyleStateเป็นแบบสองกลุ่ม โครงสร้างนี้บรรจุฟิลด์ CSS แบบ computed และฟิลด์ติดตามเค้าโครง มีเพียง applicator เท่านั้นที่เขียนกลุ่ม CSS Paint อ่านComputedStyleไม่ใช่ฟิลด์ติดตามเค้าโครงHtmlParserไม่มีเลเยอร์ เป็น orchestrator การแจงส่วน CSS การคำนวณเรขาคณิต และการส่งออก paint ต้องไม่อยู่ในนั้น
ประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ประสิทธิภาพ”สัญญาเลเยอร์เป็นโครงสร้าง จึงไม่เพิ่มต้นทุนระหว่างทำงาน HtmlStyleState::toComputedStyle() สร้างสแนปช็อตที่ไม่เปลี่ยนแปลงหนึ่งชุดสำหรับแต่ละองค์ประกอบที่ต้องการ paint สแนปช็อตนั้นช่วยให้โค้ด paint หลีกเลี่ยงถุงสถานะที่เปลี่ยนแปลงได้ ต้นทุนการเรนเดอร์ถูกควบคุมโดยโมเดลการสตรีม ไม่ใช่โดยการแบ่งเลเยอร์ performance_budget ต่อหน้า (wall_ms: 1500, peak_mb: 64) ยังคงเป็นเพดานการทำงาน
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”การแยกเลเยอร์สนับสนุนโมเดลความปลอดภัย เลเยอร์ 1 แจงส่วนและกรองค่า CSS ตามนโยบายก่อนที่โค้ดเค้าโครงหรือ paint จะเห็น ดังนั้น DefaultHtmlSecurityPolicy::isCssPropertyAllowed() จึงยังคงเป็นประตูเดียว Paint ไม่เคยอ่าน CSS ดิบที่ผู้โจมตีควบคุม ดูรายละเอียดเพิ่มเติมในโมเดลความปลอดภัยของโมดูล HTML
ความสอดคล้อง
หัวข้อที่มีชื่อว่า “ความสอดคล้อง”หน้านี้ไม่ได้อ้างอิงมาตรฐานภายนอกใด ๆ ขอบเขตของเลเยอร์มาจาก ADR-010 และ docblock ของคลาส HtmlStyleState ซึ่งเข้ารหัสสัญญาไว้ในซอร์สโค้ดแล้ว ความสอดคล้องเชิงพฤติกรรมของ CSS บันทึกไว้ในcss-resolver
บริบทเชิงพาณิชย์
หัวข้อที่มีชื่อว่า “บริบทเชิงพาณิชย์”ความสามารถระดับองค์กร ฟีเจอร์ CSS ของ Premium ใช้สี่เลเยอร์เดียวกันนี้ผ่านจุดขยายที่บันทึกไว้ ไม่มี pipeline ของ Premium ที่แยกต่างหาก ดูเมทริกซ์การรองรับ CSS