يعرض HotPDF جداول HTML عبر ملف تعريف HTML5 للوسائط المقسمة صفحات باستخدام شبكة إشغال حقيقية لـ rowspan وcolspan، وارتفاعات صفوف مقيسة لا تقديرات مبنية على عدّ الأحرف، وصفوف رؤوس تتكرر على كل صفحة تتمة. وهناك حالتان تجعلانه يرفض تكرار رأس، ومعرفتهما مقدمًا أرخص من تنقيح خلية مكررة لاحقًا
وصنف الوثائق الذي يفرض هذا هو الذي تشحنه كل فريق تقارير عاجلًا أو آجلًا: فاتورة أو تقرير امتثال يكون فيه مصدر الحقيقة HTML، ويمتد الجدول عبر أربع صفحات، ولا بد أن يكون الرأس مقروءًا في كل منها. وأي شيء دون تخطيط جداول حقيقي ينتج الفشلين اللذين يلاحظهما القراء فورًا: رأس يظهر مرة على الصفحة الأولى، وصفوف خُمّنت ارتفاعاتها من عدّ الأحرف
لماذا انتقلت قدرة الجداول إلى عارض HTML
لأن البديل يخسر النص الغني، والنص الغني هو السبب في أن المحتوى HTML من الأصل. والخطة البديهية تبدو وكأنها إعادة استخدام: HotPDF يملك أصلًا كائن جدول DOM للتخطيط بشبكة سليمة، فاجسر محلل HTML إليه واحصل على الامتدادات مجانًا. والمشكلة فيما يرسم به كائن الجدول ذاك. فخلايا تحمل نصًا ونمطًا، ومسار رسمه يُخرج نصًا عاديًا، فأي شيء احتواه HTML فعلًا أبعد من خط ولون — روابط، وأرقام عليا، وتغييرات حجم داخلية، وألوان لكل مقطع — يضيع قبل أن يصل إلى الصفحة
والاتجاه الذي ينجو من مواجهة الوثائق الحقيقية هو المعاكس. انقل قدرات محرك الجداول — شبكة الإشغال، والقياس الحقيقي، وتكرار الرأس، وأوزان الأعمدة — إلى عارض HTML، واترك تصيير النص الغني حيث يعمل أصلًا. وهذا تغيير أكبر من الجسر، وهو التغيير الذي يبقي الرابط التشعبي داخل خلية جدول رابطًا تشعبيًا
rowspan من دون union-find
الخلايا الممتدة تنشئ مجموعات صفوف ذرية، لكن الإغلاق على تلك المجموعات لا يحتاج بنية disjoint-set عامة، لأن الإشغال دائمًا مجال متصل. فخلية بـ rowspan="3" تبدأ عند الصف K تشغل الصفوف K حتى K+2 ولا شيء غير ذلك، فتنكمش معلومة المجموعة إلى علامة نهاية لكل صف
والخوارزمية سطران من القصد. حين تضع خلية ممتدة تبدأ عند K وتنتهي عند E، سجّل GroupEnd[K] := Max(GroupEnd[K], E). ثم امش الصفوف مرة واحدة باتجاه معاكس وطبّق G[R] := G[G[R]]، التي تنشر نهاية كل صف إلى الخلف عبر الامتدادات المتداخلة وتعطي الإغلاق المتعدي في تمريرة واحدة. وما تحصل عليه هو، لكل صف، آخر صف يجب أن يبقى معه على الصفحة نفسها، وهذا بالضبط ما تحتاجه خطوة ترقيم الصفحات ليقرر أين يجوز أن يقع فاصل
وتوزيع الارتفاع هو النصف الآخر. حين تحتاج خلية ممتدة مساحة رأسية أكبر مما توفره الصفوف التي تغطيها حاليًا، يذهب الفائض إلى الصف الأخير في الامتداد، لا يوزع بالتساوي عليها. عالج الخلايا الممتدة بعد استقرار ارتفاعات الصفوف العادية، ثم املأ الصف الأخير من كل امتداد. وتوزيع الفائض بالتساوي يبدو أعدل وينتج مخرجات خاطئة بجلاء: صفوف لا تحوي سوى خلايا قصيرة أحادية السطر تتضخم لأن خلية لا علاقة لها بها قبل ثلاثة صفوف صادف أنها طويلة
var
Pdf: THotPDF;
Importer: THPDFHTMLImporter;
Stats: THPDFHTMLImportStatistics;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'audit-report.pdf';
Pdf.BeginDoc;
Importer := THPDFHTMLImporter.Create(Pdf);
try
Importer.Margin := 48;
Importer.BaseFontName := 'Arial';
Importer.BaseFontSize := 10;
Importer.MaxDOMNodes := 200000;
Importer.MaxLayoutOperations := 2000000;
if Importer.RenderHTML5(SourceHtml, PrintStyleSheet) then
begin
Stats := Importer.Statistics;
Writeln('tables ', Stats.TableCount,
' page breaks ', Stats.PageBreakCount);
end;
finally
Importer.Free;
end;
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
تأخذ RenderHTML5 ورقة أنماط للمؤلف اختيارية وسيطًا ثانيًا، وهي الموضع الذي تنتمي إليه قواعد الطباعة. وأبقِ ورقة أنماط الشاشة خارجها. والملف التعريفي ذو إصدارات، وHTML5ProfileMilestones تبلّغ أي مجموعات قدرات ينفذها البناء الحالي: himParserCascade وhimPagedLayout وhimTablesForms وhimBoundedResources، فيستطيع التطبيق أن يتدهور عمدًا بدل اكتشاف فجوة في الإنتاج
القياس يجب أن يوافق الرسم توافقًا تامًا
لا يكون ارتفاع الصف صحيحًا إلا إذا لف الكود الذي يقيس الأسطر الملفوفة اللف نفسه بقاعدة الكود الذي يرسمها. وهذا يبدو بديهيًا وهو أكثر مصدر شيوعًا منفردًا لجداول لا تصف حدودها محتواها. يقيس HotPDF بعدّاد أسطر جشع، وعلى ذلك العدّاد أن يطابق دلالات اللف في مسار إخراج النص الغني في ثلاثة جوانب محددة: يكسر عند المسافات فقط، ولا يقتطع كلمة أبدًا، والكلمة الأعرض من العمود تحصل على سطر خاص بها
والمطلب الثاني هو الخط. يجب أن يجري القياس بخط الخلية نفسه، مضبوطًا عبر SetFont بالاسم الفعلي ومجموعة النمط والحجم قبل استدعاء دالة العرض، لا بأي خط صادف أنه نشط. فالنص العريض أعرض عادة بأكثر من عشرة بالمئة من العادي بالحجم نفسه، وهذا يكفي لتحويل خلية من ثلاثة أسطر إلى خلية من أربعة. وجدول خلايا رأسه عريضة ومتنها لا، قيس بخط واحد، سيكون مخطئًا تحديدًا في الصفوف التي ينظر إليها القراء أولًا
وإصابة هذا يغيّر ما تستطيع التحقق منه في اختبار. فالأثر الملاحَظ للقياس الدقيق هو تباعد الأسطر لا عدد المحارف: صف أحادي السطر ارتفاعه نحو 20 نقطة بينما تقدير عدّ الأحرف للمحتوى نفسه يتنبأ بسطرين ونحو 35. تحقق من المسافة الرأسية بين الصفوف. وتذكر أن فضاء المستخدم في PDF فيه Y يتصاعد نحو الأعلى، فرأس يجلس فوق صف متن يعني أن قيمة Y للرأس هي الأكبر، وهذا عكس ما تكتبه حدسك بإحداثيات الشاشة
متى يرفض HotPDF تكرار رأس
في حالتين، كلتاهما كانت ستنتج مخرجات خاطئة بجلاء لو تابع. الأولى كتلة رأس تحوي خلية ممتدة تمتد بعد الرأس إلى صفوف المتن. فتكرار الرأس سيرسم محتوى تلك الخلية مرة ثانية في موضع لم يعد ينتمي إليه، فيُرسم الرأس مرة واحدة ويواصل الجدول بدونه. والثانية رأس أطول من 90 بالمئة من ارتفاع الصفحة القابل للاستخدام، حيث سيترك التكرار مساحة تكاد تكون معدومة للبيانات ولن يقدّم الجدول أي تقدم للأمام
وكلا الرفضين متعمد وصامت بالتصميم، لأن البديل أسوأ. فإن لم يتكرر رأسك كنتَ تتوقعه، فافحص الترميز عن rowspan يعبر حد thead قبل أن تشك في المحرك. فنمط الترميز المنفرد هذا يفسر معظم المفاجأة
// أوزان الأعمدة تأتي من الترميز، لذا فورقة أنماط الطباعة هي
// الموضع الذي تتحكم فيه بها. العروض تعالج أوزانًا لا بكسلات
const
PrintStyleSheet =
'table { width: 100%; }' +
'thead th { font-weight: bold; background: #eee; }' +
'td.amount { text-align: right; }';
// صف رأس يحمل rowspan يعبر إلى متن الجدول يكبت تكرار الرأس.
// أبقِ الامتدادات داخل قسم واحد:
// <thead><tr><th rowspan="2">Item</th>...</tr></thead> ok
// <tr><th rowspan="3">Item</th>... امتداد إلى tbody، لا تكرار
وتتصرف عروض الأعمدة أوزانًا لا قياسات مطلقة، وهو السلوك الذي يبقي الجدول قابلًا للاستخدام حين لا يطابق المحتوى تقدير المؤلف. فعمود مُعلن بـ 30 بالمئة يحصل على نحو 30 بالمئة من العرض المتاح، لكن التوزيع يحترم العرض الأدنى الذي يحتاجه كل عمود فعلًا، فلا يفيض عمود ضيق يحمل رمزًا طويلًا لا يُكسر في صندوق الجدول بصمت
موضع هذا في خط معالجة وثائق
ويقع عمل الجداول داخل ملف تعريف الوسائط المقسمة صفحات الأوسع، وقواعد ترقيم الصفحات وميزانيات الموارد ومعالجة CSS الموصوفة في مسار استيراد HTML5 للوسائط المقسمة صفحات تنطبق دون تغيير على الوثائق الحاوية جداول. فإن كانت بياناتك لا تبدأ HTML، فمسار البناء المباشر في بناء الجداول مباشرة في PDF يتجاوز طبقة التحليل كليًا ويعطيك سلوك الشبكة نفسه عبر واجهة برمجية. ولأن ارتفاع الصف يعتمد في النهاية على مواضع كسر الأسطر، فمناقشة القياس في ضبط النص وكسر الأسطر هي القطعة الرفيقة لكل من يضبط مخرجات جدولية كثيفة
والعبرة القابلة لإعادة الاستخدام هنا لا علاقة لها بالجداول أصلًا. حين يحتاج نظام فرعي جديد قدرة يملكها نظام فرعي قديم أصلًا، اسأل أي الاثنين يملك الشيء الأصعب إعادة تنفيذه. فحساب الشبكة بضعة عشرات من الأسطر وتنتقل بسهولة. وتصيير النص الغني بروابط داخلية وأرقام عليا وتنسيق لكل مقطع ليس كذلك، فانتقلت الشبكة وبقي النص. ويشحن HotPDF كلا المسارين ضمن مكوّن HotPDF Delphi PDF، فيكون الاختيار بين مدخل HTML والبناء المباشر قرار مشروع لا قرار مكتبة