Lewati ke konten

Batasan streaming sekali jalan HTML (ADR-001)

NextPDF merender HyperText Markup Language (HTML) dalam satu lintasan maju dan tidak menyimpan pohon elemen apa pun di memori. ADR-001 mencatat pilihan ini beserta batasan yang diberlakukannya pada setiap fitur Cascading Style Sheets (CSS).

Terminal window
composer require nextpdf/core:^3

Subsistem HTML merender HTML dan CSS menjadi Portable Document Format (PDF) melalui streaming satu lintasan. ADR-001 (“Stream-based Rendering Pipeline Retention”, diterima 2026-04-06) adalah keputusan arsitektur yang mendefinisikan model ini. Halaman ini menjelaskan model tersebut, batasannya, dan ketentuan yang harus dipatuhi para kontributor.

Dalam model ini, tokenizer (HtmlTokenizer) membaca masukan satu kali saja dan menghasilkan daftar token datar. HtmlParser::processTokens() menelusuri daftar tersebut dari kiri ke kanan dan menuliskan operator content-stream PDF ke buffer string saat mencapai setiap elemen. Mesin tidak membangun grafik elemen permanen di antara pemanggilan. Setiap state yang harus bertahan melewati pemanggilan handler dialirkan melalui objek nilai snapshot (HtmlBlockCursor), bukan melalui simpul bersama. Pewarisan gaya menggunakan tumpukan push-and-pop berisi instansi HtmlStyleState datar, bukan pohon penunjuk induk.

Ini bukan model dokumen tertahan (retained-document). Mesin tidak mempertahankan pohon dokumen, tidak menata letak ulang konten yang sudah dituliskannya, dan tidak mengizinkan masukan berubah setelah parse dimulai. Batasannya tegas: NextPDF melakukan streaming dari ujung ke ujung. Perender tertahan membangun seluruh dokumen di memori terlebih dahulu; NextPDF tidak.

Dua operasi memerlukan lookahead terbatas. Keduanya merupakan pengecualian yang eksplisit dan terbatas. Penentuan ukuran kolom tabel memindai setiap baris sebelum menempatkan sel. Operasi ini menyangga baris-baris tersebut dalam buffer tabel sementara di dalam TableParser, sebuah pengecualian yang diakui secara eksplisit oleh ADR-001. Selektor relasional :has() serta selektor :last-child dan :last-of-type menggunakan pra-pindai terbatas atas daftar token datar, bukan penelusuran pohon. ADR-001 mencatat kedua pengecualian tersebut beserta batasannya.

Model ini aman untuk worker. HtmlParser dibangun sekali per permintaan, tidak pernah sebagai singleton. HtmlParser::parse() mengatur ulang setiap field pada awal setiap pemanggilan. Tidak ada state mutabel statis di jalur render, sehingga RoadRunner, Swoole, dan Laravel Octane dapat menggunakan ulang proses tanpa kebocoran state antardokumen.

Simbol-simbol berikut menegakkan batasan tersebut. Verifikasi masing-masing terhadap src/Html/.

SimbolLokasiPeran
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpTitik masuk. Mengatur ulang semua state, lalu menjalankan satu lintasan.
HtmlParser::MAX_ELEMENT_COUNT (50_000)src/Html/HtmlParser.phpBatas keras (hard cap) pada elemen yang diproses.
HtmlParser::MAX_NESTING_DEPTH (100)src/Html/HtmlParser.phpBatas keras (hard cap) pada kedalaman penyarangan.
HtmlBlockCursorsrc/Html/HtmlBlockCursor.phpSnapshot cursor. Satu-satunya mekanisme state bersama.
HtmlStyleStatesrc/Html/HtmlStyleState.phpBingkai gaya yang didorong ke tumpukan. Tanpa penunjuk induk.
TableParser::reset()src/Html/TableParser.phpPengaturan ulang wajib untuk buffer tabel sementara di antara tabel.

Anda tidak perlu mengelola model streaming secara langsung. Satu pemanggilan merender dokumen apa pun yang didukung.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Streaming render');
$doc->addPage();
$doc->writeHtml('<h1>One forward pass</h1><p>No retained tree.</p>');
$doc->save(__DIR__ . '/output/streaming.pdf');

Render dokumen besar dengan anggaran memori tetap. Batas elemen adalah batas keamanan, jadi tentukan ukuran masukan sebelum pemanggilan.

<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
use NextPDF\Exception\HtmlParsingException;
/**
* Render trusted HTML, surfacing the streaming-model limits as typed errors.
*
* @param non-empty-string $html
*/
function renderReport(string $html, string $out): void
{
$doc = Document::createStandalone();
$doc->addPage();
try {
$doc->writeHtml($html);
} catch (HtmlParsingException $e) {
// Thrown on the 10 MB input cap, the 50,000-element cap,
// or the 100-level nesting cap. These are model boundaries,
// not transient faults — do not retry.
throw $e;
}
$doc->save($out);
}
  • Batas elemen adalah penghentian keras. Mesin melempar HtmlParsingException pada MAX_ELEMENT_COUNT = 50_000. Bagi laporan yang sangat besar ke beberapa pemanggilan writeHtml() atau beberapa dokumen.
  • Batas penyarangan adalah penghentian keras. Kedalaman di atas MAX_NESTING_DEPTH = 100 akan melempar. Pembungkus yang tersarang terlalu dalam biasanya menjadi penyebabnya.
  • Batas ukuran masukan. HtmlParser::parse() menolak masukan yang lebih besar dari 10 MB sebelum tokenisasi.
  • :has() berada di balik feature gate. Pra-pindai :has() hanya berjalan ketika fitur eksperimental css.has aktif. Tanpa itu, selektor :has() tidak akan cocok.
  • Penyanggaan tabel adalah satu-satunya pohon sementara. Satu tabel yang sangat lebar atau sangat tinggi menahan baris-barisnya di memori sampai render(). TableParser membatasi buffer ini per tabel dan mengatur ulangnya di antara tabel; ini bukan pohon yang mencakup seluruh dokumen.
  • Tanpa penataan letak ulang. Konten yang sudah dituliskan tidak pernah dipindahkan. Gaya yang datang belakangan tidak dapat mengubah keluaran sebelumnya secara surut.

Model streaming menahan maksimal satu HtmlStyleState per tingkat penyarangan, dibatasi oleh MAX_NESTING_DEPTH = 100, ditambah field cursor aktif. Memori style-state dan cursor bersifat O(depth), bukan O(jumlah elemen). ADR-001 mencatat tujuan desain agar nilai ini tetap jauh di bawah grafik objek tertahan untuk masukan yang sama. Tolok ukur peak resident set size (RSS) untuk 50,000 elemen terkendali adalah target validasi empiris yang disebutkan dalam ADR-001. Tolok ukur kinerja render-pipeline HTML melacaknya dengan gerbang regresi 5% (pekerjaan yang sudah digabungkan, PR #564). Perlakukan performance_budget per halaman (wall_ms: 1500, peak_mb: 64) sebagai langit-langit operasional.

Batasan pada halaman ini juga berperan sebagai kontrol denial-of-service. DefaultHtmlSecurityPolicy menegakkan batas atas masukan 10 MB dan batas atas penyarangan 100 tingkat secara independen dari parser, sehingga dokumen berbahaya tidak dapat menghabiskan memori melalui kedalaman atau ukuran. Model streaming itu sendiri membatasi memori secara konstruktif: tidak ada grafik elemen yang dapat diperbesar oleh penyerang. Lihat model keamanan modul HTML dan kontrak lapisan untuk permukaan kebijakan selengkapnya.

Halaman ini tidak mengutip standar eksternal apa pun. Batasan ini berasal dari ADR-001 dan dari simbol sumber yang menegakkannya, yang terdaftar pada Permukaan API. Pemetaan perilaku spesifikasi CSS didokumentasikan pada css-resolver, bukan di sini.

Kapabilitas Enterprise. Arsitektur streaming identik di Core dan Premium. Premium memperluas cakupan CSS; ini tidak mengubah model sekali jalan maupun melonggarkan batasan tersebut. Lihat matriks dukungan CSS.