شغّل قارئ شاشة على فاتورة مولدة نموذجية واستمع إلى ما يجده: المجموع النهائي يُعلن قبل بنود السطر، وتذييل الصفحة يقطع فقرة، وجدول البنود يُسطّح إلى سيل كلمات بلا تمييز. تبدو الصفحة مثالية وهي فارغة دلاليا، لأن content stream يسجل ترتيب الرسم لا المعنى. جواب PDF هو شجرة البنية المعرفة في ISO 32000-1 §14.7 — هرمية منطقية للعناوين والفقرات والجداول والأشكال موضوعة فوق المحتوى المرسوم — والاقتصاد غير متوازن: إصدار البنية أثناء التوليد يكلف دقائق من الكود، بينما ترقيعها في مستندات منتهية مشروع remediation. تعرض losLab PDF Library (PDFlibPas) هذه الآلية لتطبيقات Delphi و C++Builder عبر مجموعة صغيرة من الاستدعاءات التي تلف كل عملية رسم بدورها المنطقي.
كيف يرتبط marked content بشجرة البنية
تتعاون طبقتان. في content stream، تُحاط عمليات الرسم بتسلسلات marked-content، يحمل كل منها MCID صحيحا. وفي catalog المستند، تربط شجرة البنية تلك MCIDs بهرمية عناصر typed — H1 و P و Table و Figure — مع سمات مثل النص البديل واللغة. أنواع العناصر المخصصة قانونية لكنها يجب أن تُحل إلى أدوار معيارية عبر role map (ISO 32000-1 §14.8.4)، أما المحتوى الذي لا يحمل معنى إطلاقا — القواعد والخلفيات وأثاث الصفحة المتكرر — فيُعلّم كـ artifact كي تتخطاه التقنيات المساعدة بدلا من قراءته وسط الجملة.
يحافظ PDFlibPas على الطبقتين خلف زوج أقواس واحد: تفتح BeginTag عنصر بنية وتبدأ تسلسل marked-content، وتقع استدعاءات الرسم داخله، ثم تغلق EndTag الاثنين. يحدث bookkeeping — من MCIDs، و parent tree، ومراجع الصفحات — داخليا، وهذا يزيل أكثر أجزاء tagging اليدوي عرضة للخطأ.
يمهد مفتاحان على مستوى المستند للعمل قبل فتح أي tag. تكتب SetMarkInfo علم catalog الذي يعلن أن المستند tagged، وتقرأه IsTaggedPDF مرة أخرى — وهو أول فحص رخيص عندما تقرر هل لملف وارد أي بنية تستحق الحفظ. وبالنسبة إلى اللغة، تضبط SetDocumentLanguage الافتراض للمستند وحدها، بينما تضبطه SetPDFUAMode كجزء من تفعيل إخراج PDF/UA الكامل؛ يمكن أن يكون الملف tagged بشكل مفيد من دون ادعاء مطابقة PDF/UA، وكثيرا ما يبدأ rollout المرحلي هناك تماما.
وسم أثناء الرسم، لا بعده
نمط التوليد الذي ينجح هو معاملة قوس tag كجزء من توقيع كل استدعاء رسم، لا كمرور لاحق:
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // top-left origin
Lib.SetPDFUAMode('en-US'); // bumps the save version to PDF 1.7
Lib.SetInformation(1, 'Service Manual'); // /Title is mandatory for PDF/UA
Lib.AddRoleMap('ManualTitle', 'H1'); // custom type -> standard role
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.BeginTagEx2('ManualTitle', '', '', 'en-US', '', 'h1-cover', '');
Lib.DrawText(72, 96, 'Service Manual');
Lib.EndTag;
Lib.BeginTag('Figure', 'Exploded view of the gearbox assembly', '');
Lib.AddImageFromFile('gearbox.png', 0);
Lib.EndTag;
Lib.BeginArtifact('Layout'); // page decoration: excluded from reading
// ... draw rules and background tint ...
Lib.EndArtifact;
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
ثلاثة استدعاءات في ذلك التسلسل تحمل وزن امتثال. تفعّل SetPDFUAMode إخراج PDF/UA وترفع إصدار الحفظ بصمت إلى PDF 1.7 — وهذا يصطدم بتثبيت الإصدار: مستند مقفل على PDF 1.4 عبر LockSaveVersion يرفض الحفظ برمز الخطأ 602 عندما يكون UA mode نشطا، وهي تركيبة تظهر عندما تضبط فرق مختلفة profiles الأرشفة ومتطلبات الوصول. وتكتب SetInformation(1, ...) عنوان المستند، الذي يتوقع ISO 14289 من العارضين عرضه بدلا من اسم الملف؛ غيابه أحد أكثر نتائج PDF/UA شيوعا في الواقع. وتسجل AddRoleMap النوع المخصص ManualTitle كـ H1 — تخطها، وستبلغ التشخيصات الموضحة أدناه عن الدور غير المربوط.
يستحق انضباط العناوين سياسة مقصودة لا مستويات عشوائية. يتنقل مستخدمو قارئات الشاشة عبر اختصارات العناوين، لذلك فإن قالبا يقفز من H1 إلى H3 لأن المستوى المتوسط بدا كبيرا جدا في التصميم البصري يكسر التنقل بطريقة لا تكشفها أي مراجعة بصرية — وهذا تحديدا العيب الذي يوجد diagnostic HEADING-LEVEL-SKIP لتسميته. ربط الأنماط البصرية لكل قالب بسلم عناوين ثابت مرة واحدة، في مكان واحد، يمنع الانحراف.
جداول يستطيع قارئ الشاشة التنقل فيها فعلا
لا تعني خطوط الشبكة المرسومة شيئا خارج الشاشة. ما تتنقل فيه قارئات الشاشة هو العلاقات البنيوية: أي الخلايا رؤوس، وماذا يحكم كل رأس، وكيف ترتبط خلايا البيانات بالرؤوس في التخطيطات غير المنتظمة. تتعامل استدعاءات سمات عناصر البنية مع الثلاثة كلها:
Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column'); // valid only while this TH is open
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2); // header spans the value and unit columns
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part'); // explicit binding for irregular tables
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Table
قاعدة الترتيب صارمة وتُفرض بصمت: كل استدعاء SetStructElem* ينطبق على tag المفتوح حاليا — بين BeginTag و EndTag الخاصين به — ويعيد 0 من دون رمي أي شيء عندما لا يكون tag مفتوحا أو عندما لا تنطبق السمة. الاستدعاء في الموضع الخطأ يختفي ببساطة. لف قيم الإرجاع بتأكيدات أثناء التطوير يلتقط الانحراف مبكرا؛ أما في الحقل فلا يظهر scope مفقود إلا عندما يشغل تدقيق وصول قارئ شاشة حقيقيا على الجدول. معرّفات العناصر الممررة عبر BeginTagEx2 تغذي ID tree (ISO 32000-1 §14.7.4)، وهذا ما يجعل ربط SetStructElemHeaders قابلا للحل.
تغطي عائلة السمات نفسها البنى المتبقية التي تعتمد عليها التقنيات المساعدة. تعلن SetStructElemListNumbering كيف تُوسم عناصر القائمة، فيستطيع قارئ الشاشة إعلان الموضع داخل القائمة بدلا من تلاوة رموز bullets؛ وتسجل SetStructElemBBox bounding box للأشكال والجداول، وهو ما تستخدمه عروض reflow لوضع المحتوى؛ وتوفر SetStructElemActualText نصا بديلا للمقاطع التي لا تقابل glyphs فيها أحرفا مقروءة، مثل drop caps مركبة من vector art. تتبع كلها القاعدة نفسها: الاستدعاء يرتبط بالـ tag المفتوح، أو يختفي.
Artifacts واللغة وبوابة التشخيص قبل الحفظ
أثاث الصفحة المتكرر — الرؤوس الجارية، وعلامات الطي، والعلامات المائية، وتظليل الخلفية — ينتمي داخل أقواس BeginArtifact و EndArtifact كي لا يدخل تيار القراءة أبدا. اللغة قابلة للتوريث: الافتراض للمستند يأتي من معامل SetPDFUAMode، والمقاطع بلغة أخرى تتجاوزه لكل عنصر عبر BeginTagEx أو SetStructElemLang، وهذا ما يبقي اقتباسا فرنسيا داخل دليل إنجليزي قابلا للنطق.
قبل الحفظ، تشغل GetPDFUADiagnostics فحوص المكتبة البنيوية على المستند في الذاكرة وتعيد النتائج كنص — سلسلة فارغة تعني أنه لم يُعثر على شيء. تسمي التشخيصات أخطاء التأليف الكلاسيكية مباشرة: FIGURE-NO-ALT للصور بلا نص بديل، و HEADING-LEVEL-SKIP لـ H3 يتبع H1، و ROLEMAP-UNMAPPED للأنواع المخصصة التي لم تُسجل. ربط هذا بالبناء — ولد مجموعة المستندات، وافشل عند تشخيص غير فارغ — يحول regressions الوصول إلى فشل شبيه بوقت التجميع بدلا من نتائج تدقيق. حكم المطابقة الكامل يبقى من اختصاص preflight على الملف المحفوظ، كما في PDF/A و PDF/UA preflight في Delphi، لأن بعض التطبيع يطبق أثناء التسلسل.
لتنقل التعليقات التوضيحية مقبض خاص. يتوقع PDF/UA أن يتبع اجتياز لوحة المفاتيح لحقول النماذج والروابط ترتيب البنية، وتكتب SetTabOrderMode مدخل tab-order على مستوى الصفحة الذي يحترمه العارضون، مع توفر GetTabOrderMode لتدقيق الملفات الواردة. إنه نوع المتطلبات الذي لا يلاحظه أحد حتى يفتح مستخدم يعتمد على لوحة المفاتيح فقط خطأ، وتكلفته استدعاء واحد لكل مستند.
أشجار البنية لا تنجو من كل دمج
تبقى المستندات tagged كذلك فقط إذا حفظت كل خطوة معالجة لاحقة الشجرة. داخل PDFlibPas، الحافة الحادة هي عائلة merge-list: تبادل MergeFileListFast حفظ شجرة البنية بالسرعة، وهذا صحيح لدفعات الصور الممسوحة وخاطئ تماما للتقارير tagged — الناتج يفتح جيدا، ويعرض متطابقا، وقد فقد طبقة الوصول كلها. استخدم MergeFileList الافتراضية أو المتغير strict عندما يكون أي مدخل tagged، واجعل IsTaggedPDF جزءا من assertions ما بعد التجميع كي لا تشحن دفعة مسطحة بصمت. تحمل pipelines تجميع مجموعات مستندات كبيرة مفاضلات أكثر من هذا النوع، كما تستكشف دمج وتقسيم PDF كبير و Direct Access.
تغلق حلقة التحقق خارج المكتبة: افتح الناتج في Acrobat، وافحص لوحة tags، واقرأ مستندا واحدا على الأقل من كل عائلة قوالب بقارئ شاشة حقيقي. تلتقط التشخيصات الأخطاء البنيوية؛ لكن الأذن البشرية فقط تلتقط ترتيب قراءة صحيحا تقنيا ومربكا عمليا. بناءات التقييم ومرجع API الكامل للوسم موجودة في صفحة منتج losLab PDF Library for Delphi.