Resolver CSS: cascade dan spesifisitas
Sekilas
Bagian berjudul “Sekilas”CssResolver mencocokkan selektor terhadap aliran token, mengurutkan aturan yang cocok berdasarkan layer cascade, spesifisitas, dan urutan dokumen, lalu menerapkan !important pada pass kedua.
Instalasi
Bagian berjudul “Instalasi”composer require nextpdf/core:^3Gambaran konseptual
Bagian berjudul “Gambaran konseptual”CssResolver adalah komponen Layer 1 (berdasarkan ADR-010). Komponen ini menampung aturan Cascading Style Sheets (CSS) hasil parsing dan menentukan deklarasi mana yang berlaku untuk setiap elemen. Kelas ini diekstrak dari HtmlParser agar strukturnya tetap jelas dan bersifat internal, bukan application programming interface (API) publik.
Resolver tidak memerlukan pohon dokumen. Pencocokan selektor membaca aliran token datar dan menggunakan peta indeks yang dibangun oleh HtmlChildScanner pada pipeline Tahap 3: jumlah anak, jumlah tag yang sama, dan status kosong. Peta tersebut menjawab pseudo-class struktural. Selektor relasional :has() memakai pra-pindai terbatas yang dijelaskan dalam batasan streaming.
Resolusi cascade berjalan dalam dua pass pada CssResolver::resolveMatchingProperties(). Pass 1 menerapkan deklarasi normal dalam urutan cascade: bobot layer cascade terlebih dahulu, lalu spesifisitas, kemudian urutan dokumen. Pass 2 menerapkan deklarasi !important dalam urutan spesifisitas. Deklarasi !important menggantikan deklarasi normal mana pun, tanpa memandang spesifisitas. Pemisahan menjadi dua pass ini adalah strategi implementasi yang menghasilkan kumpulan properti terselesaikan untuk dikonsumsi oleh layer tata letak.
Urutan cascade yang diterapkan resolver sejalan dengan spesifikasi CSS Cascading and Inheritance dari World Wide Web Consortium (W3C). Deklarasi pertama-tama diurutkan berdasarkan origin dan importance, lalu berdasarkan spesifisitas selektor. Jika spesifisitasnya sama, deklarasi terakhir dalam urutan dokumen yang menang (CSS Cascade 5 §6.4; lihat Kesesuaian). Komentar di dalam kode sumber pada CssResolver mengutip klausa yang sama, sehingga Anda memiliki cara ketiga untuk memverifikasi perilaku ini, selain spesifikasi dan glosarium.
Spesifisitas dihitung sebagai triple (A, B, C) dari jumlah komponen ID, class, dan type, lalu triple tersebut dibandingkan komponen demi komponen (Selectors Level 4 §16). NextPDF menghitung spesifisitas untuk setiap aturan yang cocok sebelum mengurutkan cascade.
Ada satu batasan penting. Aturan inversi layer §6.4.3 berlaku untuk deklarasi !important lintas layer cascade, dan kode sumber mencatatnya sebagai pekerjaan yang belum selesai untuk klaster pekerjaan layer cascade. Ketika layer cascade dideklarasikan dan !important melintasi layer, urutan terselesaikan dapat berbeda dari perilaku spesifikasi penuh. Matriks dukungan CSS adalah sumber rujukan utama untuk status dukungan per fitur, dan halaman ini tidak mengulang dukungan per properti.
Permukaan API
Bagian berjudul “Permukaan API”| Simbol | Lokasi | Peran |
|---|---|---|
CssResolver::parseStyleBlock(string $css, bool $nestingEnabled = false): void | src/Html/CssResolver.php | Mem-parsing blok <style> menjadi aturan. |
CssResolver::resolveMatchingProperties(...) | src/Html/CssResolver.php | Mencocokkan selektor dan menyelesaikan cascade dua pass. |
CssResolver::resolveHasSelectors(array $tokens): array | src/Html/CssResolver.php | Pra-pindai terbatas untuk :has() (di-gate). |
CssResolver::resolveFirstLetterProperties(...) | src/Html/CssResolver.php | Menyelesaikan properti ::first-letter. |
CssResolver::resolvePseudoElementProperties(...) | src/Html/CssResolver.php | Menyelesaikan properti ::before / ::after. |
CssResolver::getLayerRegistry(): LayerRegistry | src/Html/CssResolver.php | Layer cascade yang dideklarasikan. |
Contoh kode — Mulai cepat
Bagian berjudul “Contoh kode — Mulai cepat”Anda tidak memanggil resolver secara langsung. Anda menulis CSS, lalu resolver berjalan di dalam writeHtml(). Dalam cascade di bawah ini, p terselesaikan menjadi merah karena aturan class memiliki spesifisitas lebih tinggi daripada aturan type.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml( '<style>p { color: blue; } .lead { color: red; }</style>' . '<p class="lead">Higher-specificity class wins.</p>');$doc->save(__DIR__ . '/output/cascade.pdf');Contoh kode — Produksi
Bagian berjudul “Contoh kode — Produksi”Contoh ini memperlihatkan pass kedua !important. Deklarasi type !important menggantikan deklarasi class yang setara secara inline, meskipun selektor class memiliki spesifisitas lebih tinggi.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();$doc->addPage();$doc->writeHtml( '<style>p { color: green !important; } .lead { color: red; }</style>' . '<p class="lead">!important overrides higher specificity.</p>');$doc->save(__DIR__ . '/output/important.pdf');Kasus tepi & jebakan
Bagian berjudul “Kasus tepi & jebakan”!importantmengabaikan spesifisitas. Pass 2 menerapkan deklarasi!importantdalam urutan spesifisitas, dan deklarasi tersebut selalu menggantikan deklarasi normal.- Layer cascade +
!importantlintas layer. Kode sumber mencatat aturan inversi layer §6.4.3 untuk deklarasi important sebagai pekerjaan yang belum selesai. Verifikasi perilaku terhadap matriks dukungan CSS sebelum mengandalkannya. - Tanpa layer yang dideklarasikan, resolver memakai jalur cepat. Tanpa
@layer, pengurutan menyusut menjadi perilaku spesifisitas saja dan identik secara bit dengan perilaku pra-layer. :has()di-gate. Pra-pindai relasional hanya berjalan ketika fitur eksperimentalcss.hasdiaktifkan.- Pencocokan selektor berbasis aliran. Selektor struktural menggunakan peta indeks, bukan penelusuran pohon. Selektor yang membutuhkan navigasi pohon arbitrer di luar peta indeks tidak dapat diselesaikan dalam model ini.
Performa
Bagian berjudul “Performa”Dalam kasus terburuk, pencocokan selektor adalah O(rules × elements), dengan batas dari batas streaming. Dua proses pengurutan cascade adalah O(matched rules · log matched rules) per elemen. Jalur tanpa layer melewati resolusi layer sepenuhnya. performance_budget per halaman (wall_ms: 1500, peak_mb: 64) menetapkan batas operasional. Benchmark render-pipeline HTML membantu mencegah regresi (pekerjaan yang sudah digabungkan, PR #564).
Catatan keamanan
Bagian berjudul “Catatan keamanan”Resolver hanya melihat CSS yang diizinkan oleh DefaultHtmlSecurityPolicy::isCssPropertyAllowed(). Daftar izin menetapkan batas keamanan, sedangkan tabel dukungan runtime menetapkan batas kapabilitas tersendiri. Properti yang diblokir kebijakan tidak pernah mencapai cascade. Lihat model keamanan modul HTML.
Kesesuaian
Bagian berjudul “Kesesuaian”| Perilaku | Spesifikasi | Klausa | reference_id |
|---|---|---|---|
| Pengurutan cascade: origin/importance → spesifisitas → urutan kemunculan | W3C CSS Cascading and Inheritance Level 5 | §6.4 (css_cascade_5#x1.x7.x1.p21) | |
| Spesifisitas sebagai triple (A,B,C) dari jumlah ID/class/type | W3C Selectors Level 4 | §16 (selectors_4#x1.x16.p2) | |
| Parsing deterministik dan pemulihan kesalahan parsing | W3C CSS Syntax Level 3 | §4 (css_syntax_3#x1.x4.p2) |
Materi W3C berlisensi CC-BY 4.0. Klaim di atas merupakan parafrasa. Pengidentifikasi klausa dan chunk disediakan untuk verifikasi. NextPDF tidak mengklaim kesesuaian penuh terhadap modul-modul ini; lihat matriks dukungan CSS untuk status per modul yang terverifikasi.
Konteks komersial
Bagian berjudul “Konteks komersial”Kapabilitas Enterprise. Premium memperluas kumpulan properti yang dicocokkan dan diterapkan. Algoritma cascade dan model dua pass
!importantidentik di semua edisi. Lihat matriks dukungan CSS.