يستطيع PDFlibPas وسم مستند أثناء رسمه. فعّل SetAutoTagMode فتتحول استدعاءات DrawText العادية إلى فقرات، ويصبح النص المرسوم مباشرة بعد RegisterHeading عنوانًا من ذلك المستوى، وتتحول الترويسات والتذييلات الجارية إلى عناصر زخرفية يتخطاها القارئ، وتصبح الصور أشكالًا، ويحمل DrawTableRows الجدول وصفوفه وخلاياه إلى شجرة البنية
البديل — وحتى وقت قريب الخيار الوحيد — كان تغليف كل استدعاء رسم داخل BeginTag وEndTag يدويًا. هذا ينجح، وبالنسبة للمستندات ذات البنية غير الاعتيادية لا يزال الأداة الصحيحة. أما بالنسبة للتقرير أو الفاتورة أو كشف الحساب المعتاد، فمعناه أن قابلية الوصول للإخراج تتوقف على ألا ينسى أحد زوجًا واحدًا، عبر كل مسار كود يرسم أي شيء
ما الذي تغطيه وحدات النمط
يأخذ SetAutoTagMode قناعًا من البتات ويُعيد النمط الذي كان ساريًا قبلها. AUTOTAG_TEXT (1) يوسم النص كفقرة، أو كعنوان حين يحين موعده. AUTOTAG_FURNITURE (2) يعلّم الترويسات والتذييلات وأرقام الصفحات كعناصر زخرفية. AUTOTAG_FIGURE (4) يحوّل صورة مرسومة إلى شكل، أو إلى عنصر زخرفي حين تُعلَن كزخرفية. AUTOTAG_TABLE (8) يحمل الجداول المرسومة إلى شجرة البنية. AUTOTAG_DEFAULT هو 15، أي الأربعة معًا
تفعيل النمط يعلّم المستند أيضاً بأنه موسوم، وهذه الخطوة أقل تجميلية مما تبدو. يعتبر القارئ المستند غير موسوم ما لم يقل الفهرس غير ذلك (ISO 32000-1 §14.7.1)، لذا فإن ملفًا يحمل شجرة بنية كاملة بلا تصريح /MarkInfo يُعلَن من قبل التقنيات المساعدة بأنه بلا بنية إطلاقًا. الشجرة موجودة؛ لا شيء يقرؤها
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutoTagMode(AUTOTAG_DEFAULT); // text + furniture + figures + tables
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.RegisterHeading(1, 'Annual service report');
Lib.DrawText(72, 96, 'Annual service report'); // becomes H1
Lib.SetTextSize(11);
Lib.DrawText(72, 130, 'Every unit installed before 2024 was inspected.');
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
كيف يعرف العنوان أي نص ينتمي إليه؟
يسمّي RegisterHeading المستوى للنص التالي المرسوم، وينتظر النص. إن رُسمت صورة بينهما، تصبح الصورة شكلًا ويبقى العنوان معلّقًا للنص الذي يليها. هذا السلوك متعمّد: البديل، حيث تأخذ الصورة مستوى العنوان، أنتج مستندات يُعلَن فيها خط فاصل زخرفي تحت عنوان باعتباره العنوان نفسه
القاعدة نفسها لـ«تُستهلك على عنصر واحد» تحكم الأشكال. يزوّد RegisterFigure الوصف الذي تحمله الصورة التالية، ويعلن RegisterDecoration الصورة التالية كخط أو حدّ أو خلفية لا تحمل معنى. كلاهما يُستهلك بصورة واحدة، فلا ترث صورة لاحقة وصفًا مخصصًا لسابقة — وهذا بالضبط كيف ينتهي النص البديل مرتبطًا بالصورة الخطأ في الكود الموسوم يدويًا
الوصف أهم من أي سلسلة نصية أخرى في مستند قابل للوصول. قارئ غير مبصر يحصل على الوصف مكان الصورة، وهذا كل ما يحصل عليه. «رسم بياني» ليس وصفًا؛ «إيرادات ربع سنوية حسب المنطقة، مع المنطقة الشرقية الأعلى في الربع الثالث» هو وصف
Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0); // becomes a tagged Figure
Lib.RegisterDecoration; // meaningless rule
Lib.AddImageFromFile('divider.png', 0); // drawn inside a layout artifact
الجداول والترويسات وأين يقع قرار التكرار
عند تفعيل بت الجدول، يحمل DrawTableRows الجدول وصفوفه وخلاياه إلى شجرة البنية، فيستطيع القارئ تحديد العمود الذي تقع فيه قيمة بدلًا من قراءة الجدول كاملًا كامتداد من نص غير مترابط. يسمّي SetTableHeaderRowCount عدد الصفوف الأولى التي تشكّل الترويسة؛ تُكتَب تلك الصفوف كخلايا ترويسة تحمل نطاق عمود، وهذا ما يتيح للقارئ إعلان عنوان القيمة التي يقف المستخدم عندها
صفوف الترويسة المسمّاة بهذه الطريقة تبقى مكانها. تكرارها في أعلى كل صفحة قرار تخطيط، ويبقى كذلك: يأخذ DrawTaggedTableRows وسيط RepeatHeaderRows لهذا الغرض بالذات. إبقاء الاثنين منفصلين يمنع شجرة البنية من اكتساب نسخة ثانية من الترويسة عند كل فاصل صفحات، وهذا ما ينتجه التكرار التلقائي
var
TableID: Integer;
begin
TableID := Lib.CreateTable(40, 3);
Lib.SetTableHeaderRowCount(TableID, 1); // row 1 is the header band
Lib.SetTableCellContent(TableID, 1, 1, 'Part');
Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
// ... fill the data rows ...
// Draw rows 1..40 into a 600pt band, repeating one header row per page
Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;
المزج بين الوسم التلقائي واليدوي
الوسم التلقائي يتنحّى داخل وسم فُتح يدويًا. يمكن أن يُوصَف جزء من المستند بكودك ويُترَك الباقي للمكتبة، دون أن يتداخل الاثنان — وهذا هو الترتيب الذي تريده أغلب المستندات الحقيقية. صفحة الغلاف وكتلة التوقيع يحملان بنية تفهمها أنت فقط؛ المئتا صفحة من المتن بينهما لا تفعل
قاعدتا أمان تبقيان الإخراج نظيفًا. لا يُوسَم شيء داخل عنصر زخرفي، لأن المحتوى المعلَّم كعنصر زخرفي يجب ألا يحمل أي عنصر بنية. والنص الفارغ لا يفتح عنصرًا، فلا يستطيع DrawText شارد بسلسلة فارغة أن ينتج عنصر بنية يعلنه قارئ كفراغ. كلاهما من نوع العيوب التي تتراكم بهدوء في المستندات الموسومة يدويًا والتي يُبلِّغ عنها المُحقِّق دفعة واحدة بعد أشهر
ما الذي لا يزال الوسم التلقائي لا يقرره عنك
ترتيب القراءة خارج ترتيب الرسم، والأدوار الدلالية التي ليست فقرة أو عنوانًا أو شكلًا أو جدولًا، وتصريحات اللغة. الوسم التلقائي يخصّص البنية بالترتيب الذي يُرسَم به المحتوى — إن كان كود التخطيط لديك يرسم الشريط الجانبي قبل المتن، فهذا هو الترتيب الذي تسجله الشجرة. بالنسبة للمستندات التي يختلف فيها الترتيب البصري وترتيب القراءة فعلًا، تبقى واجهة الوسم اليدوي الأداة الصحيحة، ويغطي شرح البنية الموسومة وإمكانية الوصول الأدوال والنطاقات وروابط الترويسة بالتفصيل
حين ينتهي المستند، تحقّق بدلًا من الافتراض: توضّح ملاحظات فحص PDF/A وPDF/UA المسبق كيفية الحصول على حُكم على البنية التي أنتجتها، ويغطي شرح تصدير التقارير المبني على البيانات أين تناسب هذه الاستدعاءات محرك تقارير يولّد تخطيطه من البيانات
PDFlibPas مكتبة Pascal أصلية لـ PDF من أجل Delphi وC++Builder وLazarus بلا مشغّل PDF خارجي، فيُنتَج الإخراج القابل للوصول بالكود نفسه الذي يرسم المستند — انظر صفحة منتج PDFlibPas لقائمة API والمنصات الكاملة