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

مسار عرض HTML

عند استدعاء writeHtml()، ينفّذ المسار مرورًا أماميًا واحدًا على لغة ترميز النص التشعّبي (⁨HTML⁩): يحوّل المُدخل إلى رموز، ويحلّ @page والأنماط، ويُخطّط المحتوى، ويرسم مُعامِلات تنسيق المستندات المحمول (⁨PDF⁩). ولا يحتفظ بشجرة عناصر بين المراحل.

Terminal window
composer require nextpdf/core:^3

يُحوّل مسار عرض ⁨HTML⁩ مزيج ⁨HTML⁩+⁨CSS⁩، أي ⁨HTML⁩ مع صفحات الأنماط المتتالية (⁨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() قائمة الرموز مرة واحدة. ولكل عنصر، يحلّ التتالي (تكتب مُطبِّقات الطبقة 1 إلى HtmlStyleState)، ويحسب الهندسة (تخطيط الطبقة 3)، ويُصدِر مُعامِلات ⁨PDF⁩ (رسم الطبقة 4). يستخدم توارث الأنماط مكدّس HtmlStyleState القائم على الدفع والسحب. ويتنقّل المؤشّر (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⁩ ومخطّطات الروابط المسموح بها. راجع نموذج أمان وحدة ⁨HTML⁩.

تتبع تسوية نهايات الأسطر معالجة نهايات الأسطر في معيار ⁨HTML⁩: يتحوّل ⁨CRLF⁩ و⁨CR⁩ المفرد إلى ⁨LF.⁩ تُوثَّق مطابقة ⁨CSS⁩ لكل خاصية في مصفوفة دعم ⁨CSS⁩، ويُوثَّق سلوك التتالي في ⁨css-resolver⁩. ولا تُعيد هذه الصفحة عرض الدعم لكل خاصية.

قدرة ⁨Enterprise.⁩ يُوسّع ⁨Premium⁩ تغطية ⁨CSS⁩ على المسار نفسه. لا يتغيّر تسلسل المراحل الست بين الإصدارات. راجع مصفوفة دعم ⁨CSS⁩.