تُرسِم PDF Library for Delphi HTML في صفحة PDF بتخطيط ثنائي الأبعاد حقيقي: تُقاس وتُوضَع خاصيتا display: flex وdisplay: grid بدلًا من تدهورهما إلى كتل مُكدَّسة، وتُحجَز الحواشي السفلية في أسفل الصندوق الذي يحمل مرجعها، بترقيم يبقى متواصلًا عبر الأعمدة والصفحات. ونقاط الدخول هي المألوفة: DrawHTMLTextBox لصندوق واحد وDrawHTMLStory لتدفق متعدد الأعمدة
وهذا مهم لأن HTML هو الطريقة التي يصل بها معظم محتوى التقارير الآن. تُؤلَّف القوالب من أشخاص يكتبون CSS، وتُصمَّم لوحات المعلومات كبطاقات، وأي مُرسِّم يُدهور بصمت صف flex إلى أربع كتل مُكدَّسة ينتج مستندًا لا يشبه التصميم إطلاقًا. وحتى وجود هذه القدرة، كانت الحاوية الثنائية الأبعاد الوحيدة التي يقيسها المحرك هي الجدول، بحيث كان يجب إعادة تأليف كل تخطيط بطاقة كجدول يدويًا
ما الذي تغيّر في نموذج التخطيط؟
كانت الحلقة الرئيسية السابقة تحافظ على صندوق سطر واحد وتتقدم أسفل الصفحة. ويتعامل هذا النموذج مع المحتوى المضمَّن والكتل المُكدَّسة بشكل مثالي ولا يستطيع التعبير عن حاوية تُحدَّد أحجام عناصرها الفرعية بالنسبة إلى بعضها بعضًا. وكانت الجداول الاستثناء الوحيد، بقياسها الخاص ذي المرورين
وتُضيف flex وgrid كل منهما مرور قياس محدودًا على العناصر الفرعية لحاوية، والكلمة المهمة هنا هي "محدود". فحاوية flex تقيس حتى 256 عنصرًا فرعيًا مباشرًا في مصفوفة ثابتة. وتستخدم grid مصفوفة إشغال بحد أقصى 64×64 خلية لتموضع تلقائي حتمي. وتوجد هذه السقوف بحيث لا تستطيع ورقة أنماط عدائية أو مُولَّدة دفع تكرارية غير محدودة أو ذاكرة تموضع تربيعية، وهو مصدر قلق حقيقي حين يصل HTML من قالب يُحرِّره عميل
كيف تحصل عناصر flex على أحجامها
في اتجاه الصف، تجمع الحاوية أساس كل عنصر إلى جانب أوزان النمو والتقلص الخاصة به، ثم توزّع المساحة المتبقية، الموجبة أو السالبة، وفق تلك الأوزان. ومع flex-wrap، يُحَل كل سطر بشكل مستقل، بحيث يُخصِّص صف ينقسم إلى سطرين المساحة الحرة لكل سطر بدلًا من عبر الحاوية بأكملها. وفي اتجاه العمود، يعمل توزيع المحور الرئيسي نفسه مقابل ارتفاع صريح أو ارتفاع المحتوى
تعمل justify-content وalign-items وgap والاتجاهات المعكوسة على هندسة سبق قياسها. فهي تُحرِّك الصناديق؛ ولا تُطلق أبدًا إعادة قياس محتوى العنصر. وهذا الفصل هو ما يمنع لوحة معلومات معقّدة من قياس عناصرها الفرعية عدة مرات
uses
PDFlibrary;
var
Lib: TPDFlib;
Html, Remainder: WideString;
begin
Lib := TPDFlib.Create;
try
Lib.NewDocument;
Lib.SetPageSize('A4');
Lib.NewPage;
Html :=
'<div style="display:flex; gap:12px;">' +
' <div style="flex:2 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Revenue</b><br/>EUR 4,182,300</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Margin</b><br/>18.4%</div>' +
' <div style="flex:1 1 0; background:#f4f6f8; padding:8px;">' +
' <b>Backlog</b><br/>92 days</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 40, 515, 120, Html);
if Remainder <> '' then
Log('content did not fit - carry the remainder to the next box');
Lib.SaveToFile('dashboard.pdf');
finally
Lib.Free;
end;
end;
وقيمة الإرجاع هي سلسلة الاستمرار، وهي الطريقة التي تُبلغ بها كل نقطة دخول لرسم HTML عمّا لم يتّسع. مرّرها إلى الصندوق التالي أو الصفحة التالية ويستأنف التدفق من حيث توقف
تموضع Grid، وما يمكن أن يكون عليه المسار
تقبل مسارات Grid أطوالًا ثابتة، ونسبًا مئوية، ووحدة fr، وتعبيرات repeat() بسيطة، وminmax(). ويملأ التموضع التلقائي مصفوفة الإشغال بشكل حتمي، بحيث يُنتج HTML نفسه دائمًا الترتيب نفسه. ويُسمح للإحداثيات الصريحة بالتداخل، وهذا متعمَّد: فتصميم يُطبِّق شارة فوق بطاقة يُعبِّر عن نية، لا عن خطأ. وحين يُعطى محور واحد فقط صراحةً، يبحث التموضع في المحور الآخر فقط
وتُسهم العناصر التي تمتد عبر عدة صفوف بارتفاعها المقيس مرة أخرى في الصفوف التي تغطيها، بمتوسط عبرها، وهذا يمنع عنصرًا ممتدًا طويلًا من ضغط صف واحد مع ترك جيرانه قصيرة:
Html :=
'<div style="display:grid; grid-template-columns:repeat(3, 1fr); ' +
' gap:10px;">' +
' <div style="grid-row:span 2; background:#eef;">Site plan</div>' +
' <div>Inspector</div>' +
' <div>Date</div>' +
' <div style="grid-column:2 / span 2;">Findings summary</div>' +
'</div>';
Remainder := Lib.DrawHTMLTextBox(40, 180, 515, 260, Html);
وتُرسَم عناصر flex وgrid الفرعية عبر مُرسِّم HTML نفسه المستخدَم لكل شيء آخر، وهذه هي الخاصية التي تجعل الميزة قابلة للاستخدام بدلًا من عالم منفصل. فالخطوط، وتسلسل CSS، والروابط، والصور، والجداول، وحاويات flex أو grid متداخلة أخرى، كلها تتصرف داخل عنصر flex تمامًا كما تتصرف في المستوى الأعلى، وتُسجِّل خطة التخطيط الخارجية أوامر النص والمستطيل النهائية بحيث يعيد الرسم المتكرر استخدام ذاكرة القياس المخزَّنة مؤقتًا الموجودة
لماذا تُعدّ الحواشي السفلية مشكلة تقسيم إلى صفحات؟
الحاشية السفلية ليست محتوى يتدفق بعد الفقرة التي تحتوي مرجعها؛ بل محتوى يجب أن يظهر في أسفل الصندوق نفسه الذي يحتوي مرجعها. وهذا يعكس ترتيب القياس المعتاد، لأن المساحة المتاحة لنص المتن تعتمد الآن على محتوى لم يُخطَّط بعد
لذا يقيس المُرسِّم الحاشية حين يصادف المرجع، ويطرح مساحة الحاشية من ميزانية ارتفاع المتن الخاصة بالصندوق المحدود الحالي. وإذا تعذّر أن يتسع المرجع، ونص المتن حتى تلك اللحظة، والحاشية جميعًا معًا، ينتقل علامة الحاشية السفلية وكل ما بعدها إلى سلسلة الاستمرار معًا. وهذه القاعدة هي ما يمنع الإخفاقين الكلاسيكيين: حاشية تطبع فوق نص المتن، وحاشية عالقة على صفحة يقع مرجعها في الصفحة السابقة
وفي صندوق محدود، تُثبَّت مساحة الحاشية في الأسفل بخط فاصل أعلاها. وفي القياس غير المحدود، حيث لا يوجد ارتفاع صندوق للتثبيت عنده، تلي مساحة الحاشية المتن مباشرة. ويُحمَل الترقيم في حقل امتداد على كِدس الاستمرار، بحيث تُبقي DrawHTMLTextBox وDrawHTMLStory التسلسل مستمرًا عبر الأعمدة والصفحات، وتستأنف سلسلة استمرار أُنتجت قبل وجود ذلك الحقل بشكل صحيح رغم ذلك
// تحافظ الحواشي السفلية داخل قصة متعددة الأعمدة على تسلسل متواصل واحد
Html := LoadTemplate('chapter.html'); // تستخدم علامات float:footnote
Remainder := Lib.DrawHTMLStory(40, 40, 515, 700,
2, // أعمدة
16, // المسافة الفاصلة بالنقاط
20, // أقصى عدد صفحات لهذه القصة
Html);
if Remainder <> '' then
Log('story exceeded its page budget');
إرشادات عملية لمؤلفي القوالب
صمِّم ضمن السقوف الموثَّقة. فحاوية flex بأكثر من 256 عنصرًا فرعيًا مباشرًا هي دائمًا تقريبًا جدول بيانات يرتدي زي flex، ومسار الجدول يقيسها بشكل أفضل على أي حال. وgrid أكبر من 64×64 هو جدول بيانات، وتنطبق النصيحة نفسها. وبالنسبة إلى نص المتن متعدد الأعمدة، يحكم سلوك الأعمدة والفصل بالشرطة الموصوف في الفصل بالشرطة وأعمدة النص المتوازنة شكل التدفق داخل كل عمود
قِس قبل أن ترسم حين يجب أن يتّسع تخطيط. تُبلغ GetHTMLTextHeight عن الارتفاع الذي سيحتاجه عرض معطى، وهي الطريقة الرخيصة للاختيار بين تخطيط وآخر قبل الالتزام بالحبر. وعامِل سلسلة استمرار غير فارغة كأمر عادي لا استثنائي: فهي الآلية التي يُقسَّم بها المحتوى الطويل إلى صفحات، لا إشارة خطأ
وحين يصل HTML من محرك تقارير بدلًا من قوالب مكتوبة يدويًا، فإن المسار المدفوع بمجموعة البيانات في محرك تقارير مجموعة البيانات يتناغم جيدًا مع هذا، فيُولِّد الترميز الذي تُرتِّبه flex وgrid بعد ذلك. وحين يجب أن يغادر المحتوى نفسه ملف PDF مرة أخرى، فإن مسار التصدير الدلالي في تصدير PDF إلى Markdown وDOCX يُغلق الحلقة الكاملة
وتخطيط HTML، وتوليد التقارير، والتصدير الدلالي جزء من مكتبة واحدة لـ Delphi وC++Builder وFree Pascal؛ وتوجد قائمة الميزات الكاملة على صفحة PDF Library for Delphi