تخطَّ إلى المحتوى

قيود التدفّق أحادي المرور في HTML (ADR-001)

يعرض ⁨NextPDF⁩ مستندات ⁨HTML⁩ بمرور أمامي واحد، ولا يحتفظ بأي شجرة عناصر في الذاكرة. يوثّق ⁨ADR-001⁩ هذا الاختيار والقيود التي يفرضها على كل خاصية من خصائص ⁨CSS⁩.

Terminal window
composer require nextpdf/core:^3

يعرض نظام ⁨HTML⁩ الفرعي ⁨HTML⁩ و⁨CSS⁩ إلى ⁨PDF⁩ عبر مرور تدفّق واحد. إنّ ⁨ADR-001⁩ (“⁨Stream-based Rendering Pipeline Retention⁩”، المعتمد 2026-04-06) هو قرار البنية الذي يعرّف هذا النموذج. تشرح هذه الصفحة النموذج وحدوده والقيود التي يجب على المساهمين الالتزام بها.

في هذا النموذج، يقرأ المُجزّئ المعجمي (HtmlTokenizer) المُدخل مرة واحدة وينتج قائمة رموز مسطّحة. يمرّ HtmlParser::processTokens() على تلك القائمة من اليسار إلى اليمين، ويكتب مُعاملات دفق محتوى ⁨PDF⁩ في مخزّن سلسلة مؤقّت عند بلوغ كلّ عنصر. لا ينشئ المحرّك أي رسم بياني دائم للعناصر بين الاستدعاءات. وأي حالة يجب أن تبقى بعد استدعاء المعالِج تنتقل عبر كائن قيمة لقطة (HtmlBlockCursor)، لا عبر عُقد مشتركة. يستخدم توارث الأنماط مكدّس دفع وإخراج من مثيلات HtmlStyleState المسطّحة، لا شجرة ذات مؤشّرات إلى الأصل.

هذا ليس نموذجًا محتفِظًا بالمستند. لا يحتفظ المحرّك بشجرة مستند، ولا يعيد تخطيط المحتوى الذي كتبه بالفعل، ولا يسمح للمُدخل بأن يتغيّر بعد بدء التحليل. الحدّ واضح: يتدفّق ⁨NextPDF⁩ من البداية إلى النهاية. يبني العارض المحتفِظ المستند كاملًا في الذاكرة أولًا؛ أمّا ⁨NextPDF⁩ فلا يفعل ذلك.

تحتاج عمليتان إلى استباق محدود. كلتاهما استثناءان صريحان ومحدودان. يفحص تحديد حجم أعمدة الجدول كلّ صفّ قبل وضع أي خلية. تُخزَّن تلك الصفوف مؤقّتًا في مخزّن جدول زائل داخل TableParser، وهو استثناء يقرّ به ⁨ADR-001⁩ بالاسم. يستخدم المُحدِّد العلائقي :has() والمُحدِّدان :last-child و:last-of-type فحصًا مسبقًا محدودًا على قائمة الرموز المسطّحة، لا اجتيازًا للشجرة. يسجّل ⁨ADR-001⁩ كلا الاستثناءين وحدودهما.

النموذج آمن للعمل ضمن العُمّال (⁨worker-safe⁩). يُنشأ HtmlParser مرة واحدة لكل طلب، ولا يُستخدم قطّ بوصفه نمطًا مفردًا (⁨singleton⁩). يعيد HtmlParser::parse() تعيين كلّ حقل في بداية كلّ استدعاء. لا توجد حالة ساكنة قابلة للتغيير في مسار العرض، لذلك يستطيع ⁨RoadRunner⁩ و⁨Swoole⁩ و⁨Laravel Octane⁩ إعادة استخدام العملية دون أن تتسرّب الحالة بين المستندات.

تفرض الرموز أدناه هذه القيود. تحقّق من كلّ رمز منها مقابل src/Html/.

الرمزالموقعالدور
HtmlParser::parse(string $html): HtmlRenderResultsrc/Html/HtmlParser.phpنقطة الدخول. يعيد تعيين كلّ الحالة، ثم يشغّل المرور الواحد.
HtmlParser::MAX_ELEMENT_COUNT (50_000)src/Html/HtmlParser.phpحدّ صارم لعدد العناصر المُعالَجة.
HtmlParser::MAX_NESTING_DEPTH (100)src/Html/HtmlParser.phpحدّ صارم لعمق التداخل.
HtmlBlockCursorsrc/Html/HtmlBlockCursor.phpلقطة المؤشّر. آلية الحالة المشتركة الوحيدة.
HtmlStyleStatesrc/Html/HtmlStyleState.phpإطار نمط مدفوع إلى المكدّس. بلا مؤشّر إلى الأصل.
TableParser::reset()src/Html/TableParser.phpإعادة تعيين إلزامية لمخزّن الجدول الزائل بين الجداول.

لا تُدِر نموذج التدفّق بنفسك مباشرةً. يعرض استدعاء واحد أي مستند مدعوم.

<?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');

اعرض مستندًا كبيرًا ضمن ميزانية ذاكرة ثابتة. حدّ العناصر هو حدّ الأمان، لذا قدّر حجم المُدخل قبل الاستدعاء.

<?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);
}
  • حدّ العناصر توقُّف صارم. يطرح المحرّك HtmlParsingException عند MAX_ELEMENT_COUNT = 50_000. قسّم التقارير الكبيرة جدًا إلى استدعاءات writeHtml() متعدّدة أو مستندات متعدّدة.
  • حدّ التداخل توقُّف صارم. يطرح العمق الذي يتجاوز MAX_NESTING_DEPTH = 100 استثناءً. غالبًا ما تتسبّب الأغلفة ذات التداخل العميق في ذلك.
  • حدّ حجم المُدخل. يرفض HtmlParser::parse() المُدخل الذي يتجاوز 10 ⁨MB⁩ قبل التجزئة المعجمية.
  • :has() مُقيّد ببوابة. لا يُشغَّل الفحص المسبق لـ:has() إلا عندما تكون الميزة التجريبية css.has نشطة. من دونها، لا تتطابق مُحدِّدات :has().
  • التخزين المؤقّت للجداول هو الشجرة الزائلة الوحيدة. يحتفظ جدول واحد عريض جدًا أو طويل جدًا بصفوفه في الذاكرة حتى render(). يحدّ TableParser هذا المخزّن لكل جدول ويعيد تعيينه بين الجداول؛ فهو ليس شجرة على مستوى المستند كاملًا.
  • لا إعادة تخطيط. لا يُنقَل قطّ المحتوى الذي كُتب بالفعل. لا يستطيع نمط متأخّر أن يغيّر المُخرَج السابق بأثر رجعي.

يحتفظ نموذج التدفّق بمثيل HtmlStyleState واحد على الأكثر لكل مستوى تداخل، محدودًا بـMAX_NESTING_DEPTH = 100، إضافةً إلى حقول المؤشّر النشطة. ذاكرة حالة النمط والمؤشّر هي ⁨O⁩(⁨depth⁩)، لا ⁨O⁩(⁨element count⁩). يسجّل ⁨ADR-001⁩ قصد التصميم بأن تبقى هذه الذاكرة أدنى بكثير من رسم بياني محتفِظ بالكائنات للمُدخل نفسه. إنّ معيار قياس حجم المجموعة المقيمة الأقصى (⁨RSS⁩) المضبوط عند 50,000 عنصر هو هدف التحقّق التجريبي المسمّى في ⁨ADR-001.⁩ يتتبّعه معيار قياس أداء مسار عرض ⁨HTML⁩ ببوابة انحدار 5% (عمل مدموج، ⁨PR⁩ #564). عامِل performance_budget لكل صفحة (wall_ms: 1500، peak_mb: 64) بوصفه السقف التشغيلي.

تعمل الحدود في هذه الصفحة أيضًا بوصفها ضوابط لمنع الخدمة (⁨denial-of-service⁩). يفرض DefaultHtmlSecurityPolicy سقفًا للمُدخل قدره 10 ⁨MB⁩ وسقف تداخل من 100 مستوى بصورة مستقلّة عن المُحلِّل، بحيث لا يستطيع مستند معادٍ استنزاف الذاكرة عبر العمق أو الحجم. ويحدّ نموذج التدفّق نفسه الذاكرة بحكم البناء: لا يوجد رسم بياني للعناصر يمكن لمهاجِم تضخيمه. راجع نموذج أمان وحدة ⁨HTML⁩ وعقود الطبقات للاطّلاع على سطح السياسة كاملًا.

لا تستشهد هذه الصفحة بأي معيار خارجي. تأتي هذه القيود من ⁨ADR-001⁩ ومن الرموز المصدرية المُنفِّذة المُدرَجة ضمن سطح واجهة برمجة التطبيقات. أمّا تعيينات مواصفات ⁨CSS⁩ السلوكية فموثّقة في ⁨css-resolver⁩، لا هنا.

قدرة المؤسّسات (⁨Enterprise⁩). بنية التدفّق متطابقة في ⁨Core⁩ و⁨Premium.⁩ يوسّع ⁨Premium⁩ تغطية ⁨CSS⁩؛ ولا يغيّر النموذج أحادي المرور ولا يخفّف هذه الحدود. راجع مصفوفة دعم ⁨CSS⁩.