مقال تقني

قوالب PDF/VCR-1 ونوائبها في Delphi عبر PDFlibPas

تنفذ PDFlibPas معيار PDF/VCR-1 كملف تعريف بنية موسومة: يعيش تعريف الاستبدال تحت StructTreeRoot كفرع ابن مباشر، حيث يحمل جذر القالب /O /GTS_Template للإعلان عن مجموعة الحقول، ويحمل كل نائب /O /GTS_Replacement لربط حقل بيانات واحد. لا شيء من هذا قاموس على مستوى الصفحة

تلك الجملة الواحدة هي الجزء الذي تخطئ فيه معظم التطبيقات، وخطؤها يكلف أكثر من فشل تحقق. مهمة بيانات متغيرة خُطتها الاستبدال مُقروءة بشكل خاطئ لا تتعطل. إنها تُطبع، بكميات كبيرة، بالحقل الخاطئ في الصندوق الخاطئ، وأول من يلاحظ هو العميل الذي يحمل خمسين ألفاً من الرسائل البريدية

أين يعيش تعريف الاستبدال فعلاً

يضع PDF/VCR-1 نموذج استبداله في التسلسل الهرمي للبنية المنطقية الموصوف في ISO 32000-1 §14.7، لا في قاموس صفحة خاص. جذر القالب عنصر بنية معلّق مباشرة على StructTreeRoot، يتميز بمالك سمة GTS_Template، ويعلن عن مفردات الحقول كلها في مكان واحد عبر GTS_Fields زائد GTS_Pages اختياري يسمي الحقل الذي يقود اختيار الصفحة

النوائب عناصر بنية متداخلة داخل ذلك الجذر، بمالك سمة GTS_Replacement. يحمل كل واحد GTS_Generator (الذي تتطلب PDFlibPas أن يكون PassThrough)، وقيمة GTS_Data تسمي حقل واحد معلن بالضبط، و GTS_BBox اختيارياً. الربط بالصفحة ليس تعليقاً توضيحياً على الصفحة أيضاً: يشير العنصر إلى صفحته عبر /Pg وإلى تسلسل المحتوى المعلّم عبر /K. ثم يحمل اختيار البنية على قاموس الصفحة عاقبة يسهل قولها ويسهل التقليل من شأنها: شجرة البنية بيان عام يُوصل إليه من الكتالوج، لذا فإن أي شيء يستهلك الملف التعريفي عليه أن يكون ممشطاً للشجرة بكل الأخطار التي يعنيها ذلك، لا قارئ قواميس. إذا لم تكن قد تعاملت مع شجرة البنية من قبل، فالآليات موضحة في بنية PDF الموسومة وإمكانية الوصول

أين تضع PDFlibPas نموذج استبدال PDF/VCR-1: جذر القالب معلّق مباشرة على StructTreeRoot بمالك سمة GTS_Template يعلن GTS_Fields و GTS_Pages الاختياري، بينما يحمل كل نائب متداخل بالداخل GTS_Replacement و GTS_Generator بقيمة PassThrough وحقل GTS_Data واحد و GTS_BBox اختيارياً، ويشير إلى صفحته عبر /Pg ومحتواه عبر /K
تعريف الاستبدال يعيش في التسلسل الهرمي للبنية المنطقية لا في قاموس صفحة، ما يجعل كل مستهلك ممشطاً للشجرة ويحوّل العنصر إلى عقدة بيانية يمكن لمهاجم تشكيلها

لماذا يتيح فحص أول فرع علوي مرور الملفات السيئة؟

لأن StructTreeRoot قد يحمل أبناءً مباشرين كثيرين، والمدقق الذي يتوقف بعد تحديد جذر القالب لا يرى بقيتهم أبداً. هذا هو الفشل المحدد الذي بُنيت PDFlibPas لتجنبه، وينتج بالضبط النوع من النجاح الكاذب الأسوأ من عدم وجود فحص أصلاً: ملف يبلّغ عن المطابقة ويحوي جذر قالب ثانٍ، أو نائباً يجلس خارج فرع القالب حيث لن يحترمه أي مستهلك

لذا تشغّل ValidatePDFVCRStructure في PDFlibPas مسحتين على نفس الشجرة. المسحة الأولى تعدّ كل ابن مباشر لـ StructTreeRoot وتحصي الحاملين لسمة GTS_Template؛ أي شيء غير واحد بالضبط يُرفض قبل قراءة نائب واحد. المسحة الثانية تنزل إلى كل الفروع العليا، لا فرع القالب فقط، وترفض شيئين دفعة واحدة: عنصراً متداخلاً يحمل GTS_Template وليس هو الجذر، وأي عنصر GTS_Replacement يوجد خارج فرع القالب

الاجتياز محدود عند 64 مستوى عمقاً ومليون عقدة، ويسجّل رقم الكائن ورقم الجيل لكل عنصر يزوره بحيث يُبلَّغ عن الحلقة أو المرجع المتكرر بدلاً من اتباعه. تلك الأرقام ليست ضبط أداء. شجرة البنية بيان كائنات يتحكم به مهاجم، والمدقق الذي يعيد التكرار بلا سقف هو فيض مكدس بامتداد ملف

تحقق ثنائي المسح لـ ValidatePDFVCRStructure في PDFlibPas: المسحة الأولى تعدّ الأبناء المباشرين لـ StructTreeRoot الحاملين لسمة GTS_Template وترفض أي شيء غير واحد بالضبط، والمسحة الثانية تنزل إلى كل فرع علوي لرفض جذر قالب متداخل وأي GTS_Replacement خارج فرع القالب، محدودة عند 64 مستوى ومليون عقدة
المدقق الذي يتوقف بعد تحديد جذر القالب الأول لا يرى قالباً ثانياً أبداً، لذا يغطي الاجتياز كل الفروع العليا ويسجّل أرقام الكائنات بحيث يُبلَّغ عن الحلقة بدلاً من اتباعها

تأليف قالب، والقيود التي ترفض الواجهة أن تنحني لها

تكشف PDFlibPas عن التأليف عبر واجهة تأطيرية تفرض القواعد البنيوية وقت الكتابة بدلاً من اكتشافها وقت الحفظ. يبدّل SetPDFVCRMode المستند إلى الملف التعريفي، وهو داخلياً يتطلب الوضع الأساسي PDF/X-4 من ISO 15930-7 ويشغّل علم معلومات العلامات، لأن ملف PDF/VCR-1 بلا محتوى معلّم ليس لديه ما يشير إليه النائب. من هناك، يأخذ BeginPDFVCRTemplate قائمة الحقول وحقل اختيار الصفحة، وهو صارم بطرق تستحق المعرفة قبل تصميم مخطط بياناتك. تمر أسماء الحقول عبر PDFVCRValidFieldName، الذي يقبل فقط حروف ASCII والرقام والشرطة السفلية والشرطة والنقطة، لأنها الأحرف التي تتسلسل إلى اسم PDF دون غموض في التهريب. يُبقي PDFVCRNormalizeFieldList القائمة حساسة لحالة الأحرف ويرفض التكرارات outright، ويجب أن يظهر حقل اختيار الصفحة نفسه في القائمة المعلنة

var
  Lib: TPDFlib;
  Diag: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;

    if Lib.SetPDFVCRMode(1) <> 1 then
      raise Exception.Create('PDF/VCR mode was refused');

    Lib.NewPage;
    if Lib.BeginPDFVCRTemplate('Salutation,FullName,AccountNo',
                               'AccountNo') <> 1 then
      raise Exception.Create('Field list rejected');

    // نائب واحد يربط حقل معلن واحد بالضبط.
    Lib.BeginPDFVCRPlaceholder('FullName', 72, 690, 320, 712);
    Lib.DrawText(72, 694, 'SAMPLE NAME');
    Lib.EndPDFVCRPlaceholder;

    Lib.EndPDFVCRTemplate;

    Diag := Lib.GetPDFVCRDiagnostics;
    if Diag <> '' then
    begin
      Writeln('PDF/VCR blockers: ', Diag);
      Exit;
    end;

    Lib.SaveToFile('campaign-template.pdf');
  finally
    Lib.Free;
  end;
end;

لاحظ ما تشتريه لك الإحاطة. يرفض BeginPDFVCRPlaceholder الفتح ما لم يكن قالب نشطاً ومكدس العلامات بعمق مستوى واحد بالضبط، وهكذا تضمن PDFlibPas أن النوائب أبناء مباشرون لجذر القالب ولا تتداخل أبداً داخل بعضها. تُتحقق الإحداثيات أيضاً، رافضةً NaN واللانهاية والمستطيلات المقلوبة، لذا يفشل الصندوق المحيط السيئ عند الاستدعاء لا في تقرير فحص مسبق بعد أسبوع

لماذا يحتاج النائب مسار تسلسل MCID خاصاً به؟

لأن PDF/VCR-1 يريد أن يكون /K عدداً صحيحاً عارياً، وهذه ليست الطريقة التي يسلسل بها كاتب PDF الموسوم العام الرابط بين عنصر البنية ومحتواه. بموجب ISO 32000-1 §14.6، يشير عنصر البنية عادة إلى المحتوى المعلّم إما عبر عدد MCID مباشر، عندما يكون العنصر قد حدد صفحته بالفعل عبر /Pg، أو عبر قاموس مرجع محتوى معلّم يسمي الصفحة صراحة. تميل المكتبة التي تصدر مخرجات موسومة لأغراض كثيرة إلى التوحيد على شكل القاموس لأنه يتركّب أفضل عبر الصفحات

اتخذت PDFlibPas الخيار الضيق هنا لا العام. يعلّم SetCurrentStructElemDirectMCIDKid عنصر البنية الحالي، والعناصر الحاملة لذلك العلم فقط هي التي تسلسل /K كـ MCID مباشر مع حمل الصفحة على /Pg؛ كل شيء آخر يبقي مسار قاموس MCR الموجود دون مساس. هذه مقايضة متعمدة وهي من النوع الذي يستحق التسمية بصوت عالٍ: إضافة ملف تعريف يجب ألا تغيّر بايتات المخرجات التي ينتجها كل مستند موسوم آخر أصلاً، لذا يحصل الملف التعريفي على مسار خاص به بدل مفتاح عام. كل ملف موسوم قائم يحتفظ بتسلسله السابق، ويبقى سطح الانحدار بحجم الميزة الجديدة

اجتياز واحد، مستهلكان

تشتق PDFlibPas بيان الاستبدال وحكم الامتثال من نفس مسحة المسحين، وهذا سبب إمكانية الوثوق بالبيان كخطة عمل. يملأ الاجتياز سجل TPDFVCRManifestData بينما يتحقق: Fields من جذر القالب، و PageSelectionField عندما يُعلن واحد، ومصفوفة Placeholders من إدخالات TPDFVCRPlaceholderInfo تحمل FieldName و PageNumber و MCID و HasBBox و BBox. نقطة دخول الامتثال هي CheckCompliancePDFVCR(InputStream, Password, Options, ResultList)، وتشغّل فحوصاتها بترتيب التبعية لا دفعة واحدة: فحص PDF/X الكامل أولاً، ثم GTS_PDFXVersion ضد PDF/X-4، ثم GTS_PDFVCRVersion المقروء من نطاق http://www.npes.org/pdfvcr/ns/id/، وعندها فقط مسح البنية. تمرير Options بقيمة 1 يجعلها تتوقف عند أول مشكلة، وهو ما تريده في بوابة الدفعات؛ تمرير 0 يجمع كل شيء، وهو ما تريده في تقرير

اجتياز واحد، مستهلكان في PDFlibPas: نفس المسحة المحدودة تملأ TPDFVCRManifestData بالحقول Fields و PageSelectionField ومصفوفة Placeholders من إدخالات TPDFVCRPlaceholderInfo، بينما تشغّل CheckCompliancePDFVCR فحص PDF/X الكامل ثم GTS_PDFXVersion ثم GTS_PDFVCRVersion من نطاق npes.org وعندها فقط مسح البنية، معيدة 100 فقط عندما ي qualify الملف
البيان عقد، لذا يعيد BuildPDFVCRManifest JSON فقط عندما تعود قائمة المشاكل فارغة ويبلغ عن خطأ 608 بخلاف ذلك، مانعاً ملفاً غير مطابق من التظاهر بأنه خطة عمل جديرة بالثقة
// تحقق من قالب استلمته لا أنتجته
var
  Issues: TStringList;
  Stream: TFileStream;
  I: Integer;
begin
  Issues := TStringList.Create;
  Stream := TFileStream.Create('supplier-template.pdf',
                               fmOpenRead or fmShareDenyWrite);
  try
    if CheckCompliancePDFVCR(Stream, '', 0, Issues) <> 100 then
      for I := 0 to Issues.Count - 1 do
        Writeln('PDF/VCR: ', Issues[I]);
  finally
    Stream.Free;
    Issues.Free;
  end;
end;

البيان عقد، لذا فهو موجود فقط للملفات المطابقة

يعيد BuildPDFVCRManifest JSON فقط عندما تعود قائمة المشاكل فارغة، ويعيد نصاً فارغاً بخلاف ذلك. هذا ليس ترميزاً دفاعياً من أجل ذاته. البيان يستهلكه محرك تركيب سيقود منه مخرجات حقيقية، وإعادة قائمة حقول جزئية مستخرجة من ملف بجذري قالب تدعو المحرك إلى معاملة بنية غير مطابقة كخطة استبدال جديرة بالثقة. عبر واجهة المستند، يلف GetPDFVCRManifest(InputFileName, Password) نفس الاستدعاء ويضبط رمز الخطأ الأخير على PDFLIB_ERROR_PDFVCR_COMPLIANCE (608) عندما لا يستوفي الملف الشروط، لذا يمكن تمييز الاستخراج الفاشل عن القالب الفارغ. يبلغ JSON عن fields و pageSelectionField كنص أو null ومصفوفة placeholders تعطي إدخالاتها field و page و mcid وإما bbox من أربعة أرقام أو null

var
  Lib: TPDFlib;
  Manifest: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Manifest := Lib.GetPDFVCRManifest('campaign-template.pdf', '');
    if Manifest = '' then
      // 608 تعني أن الملف غير مطابق لـ PDF/VCR-1،
      // 401 تعني أن الملف تعذر فتحه
      Writeln('No manifest, error ', Lib.LastErrorCode)
    else
      Writeln(Manifest);
  finally
    Lib.Free;
  end;
end;

تفصيل واحد تحمله إلى مستهلكك: قيمة page تتبع اصطلاح الصفحة الصفري المعمم في المكتبة، لا الترقيم الذي يبدأ من 1 الذي يراه الإنسان في العارض. خلط هذين خطأ صامت بمقدار واحد يحط المحتوى على الورقة الخاطئة، ولن يلتقطه أي مدقق لأن كلا الرقمين صالح بنيوياً

أين الحدود

يغطي دعم PDF/VCR-1 في PDFlibPas تأليف القالب وبوابة الحفظ والفحص المسبق المستقل واستخراج البيان. وهو لا ينفذ الاستبدال. تعويض البيانات في النوائب وإنتاج النسخ المخصصة هو خطوة التركيب، وهي تعيش خارج المكتبة عمداً، لأن البيان هو بالضبط الواجهة التي تتيح كتابة محرك تركيب مستقلاً. كما أن بوابة الحفظ ترفض لا تصلح. بخلاف الملفات التعريفية حيث يمكن تسوية علم تعليق خاطئ بصمت، كل قيد PDF/VCR-1 يفرضه PDFVCRReadyForSave هو قيد يجعل التخمين فيه يغيّر معنى المستند: اختلاق جذر قالب، أو اختيار الحقل الذي ينتمي إليه نائب غير مربوط، ينتج ملفاً يتحقق ويكذب. الخط نفسه بين الإصلاح والرفض نوقش لملف تعريفي مختلف في وضع التأليف PDF/E-1 والفحص المسبق المحدود، وسير العمل لتشغيل عدة فحوصات مطابقة على مستند واحد مغطى في منضدة الامتثال والتوقيع

التأليف ومسح البنية المحدود وبوابة الحفظ ومستخرج البيان تشحن جميعها في PDFlibPas Delphi PDF library، مما يعني أن القالب يمكن إنتاجه تحت الملف التعريفي ثم التحقق منه عبر مسار كود منفصل لم يرَ حالة الكاتب أبداً، وهذا الفصل هو السبب الوحيد الذي يجعل ادعاء المطابقة يساوي شيئاً