ترسل مطبعة تحويلية دفعة بيانك من 80,000 صفحة إليك مع رفض من سطر واحد: "ليس PDF/VT، لا يمكن لـ RIP التخزين المؤقت." الملف يفتح بشكل طبيعي في كل عارض على مكتبك، والألوان صحيحة، والبيانات دُمجت كما ينبغي. لكن لا شيء من ذلك هو ما طلبته المطبعة الرقمية. الطباعة عالية السرعة للبيانات المتغيرة تعيش أو تموت بقدرة المطبعة على التعرف إلى أن كتلة شعار العميل في الصفحة 1 هي البايتات نفسها تمامًا للكائن الموجود في الصفحة 40,000، ثم عرضها مرة واحدة وإعادة استخدامها. PDF/VT هو المعيار الذي يجعل هذا الوعد قابلاً للفحص آليًا، و"يبدو صحيحًا" هو الفخ بعينه، لأن البنية التي يقرأها RIP غير مرئية على الشاشة
يفضح PDFiumPas تلك البنية عبر سطح صغير على TPdf: SaveAsPdfVT يكتبها، ValidatePdfVT يفحصها. هذه المقالة تشرح ما الذي تضعه هاتان الطريقتان فعليًا على القرص وما الذي تفحصانه، وأين يكون ISO 16612-2 أشد مما يبدو في الوهلة الأولى، وأي الأجزاء هي مرساة بنيوية صادقة بدلًا من فحص مسبق كامل يمكنك أن تحاسب العميل عليه
ما الذي يعيّره PDF/VT، ولماذا يأتي PDF/X أولًا
PDF/VT (ISO 16612-2:2010) ليس صيغة ملف جديدة. إنه طبقة من بيانات التحسين المضافة فوق ملف PDF/X، وهذا الترتيب نفسه يحمل العبء. يعرّف المعيار ثلاثة مستويات توافق، لكن اثنين فقط منها يذكران ملف PDF: PDF/VT-1، مستندًا واحدًا قائمًا بذاته، وPDF/VT-2، نموذج مجموعة ملفات حيث تشير الصفحات إلى موارد خارجية مشتركة. أما الرمز الثالث الذي قد تراه، PDF/VT-2s، فليس قيمة على مستوى الملف أصلًا؛ إنه يعيش في ترويسة تيار MIME موصوفة في الملحق A. إذا وجدت شفرة تضع GTS_PDFVTVersion = "PDF/VT-2s" في XMP لمستند، فتلك الشفرة خاطئة
القاعدة غير القابلة للمساومة في الملف الواحد هي أساس PDF/X. يقتضي ISO 16612-2 §6.2.1 أن يكون كل ملف PDF/VT-1 أيضًا ملف PDF/X-4 صالحًا. أما مجموعة ملفات PDF/VT-2، فبحسب §6.2.2، فيجب أن تقوم بدلًا من ذلك على PDF/X-4p أو PDF/X-5g أو PDF/X-5pg. ولهذا لا يستطيع كاتب PDF/VT أن يضيف بضع مفاتيح تعريف فقط: عليه أن يحمل معه مجموعة العلامات الكاملة لـ PDF/X-4، وهذا يعني OutputIntent وملف ICC مقصدًا مضمنًا، وXMP مطابقًا، وإدخالات Info الخاصة بالمستند، وترويسة /ID، ومن دون تشفير. إذا تجاوزت أيًا من ذلك فسيكون لديك ملف يدّعي PDF/VT ويفشل لحظة يفحص مستهلك مطابق الأساس. يتعامل PDFiumPas مع طبقة PDF/X-4 بوصفها جزءًا من حفظ PDF/VT، لذلك لا تستدعي خطوة SaveAsPdfX منفصلة أولًا؛ فالحاقن يكتب الطبقتين في تمريرة واحدة
كتابة ملف باستخدام SaveAsPdfVT
الاستدعاء الأدنى لا يحتاج إلا إلى مستند فعّال، لأن TPdfVTSaveOptions.Default يوفّر ملف ICC sRGB مدمجًا وpvc1. يجري الحفظ في ثلاث خطوات داخليًا: يزيل أي أمان (إدخال العلامات النصية في تيار كائن مشفّر سيفسده)، ويربط Info و/ID الموجودين في المستند مع مجموعة العلامات حتى تتوافق قيم XMP وInfo، ثم يلحق كائنات PDF/X-4 وPDF/VT عبر تحديث تزايدي
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
if Pdf.LoadFromFile('statements-merged.pdf') then
begin
// Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
Writeln('PDF/VT-1 written')
else
Writeln('Save failed (document not active?)');
end;
finally
Pdf.Free;
end;
end;
في مخرجات الإنتاج الحقيقية تريد غالبًا أن تستبدل OutputIntent بخصائص مطبعتك، لا ببديل sRGB العام. زوّد بايتات ICC ومعرّفات الشرط عبر TPdfVTSaveOptions:
var
Pdf: TPdf;
Opt: TPdfVTSaveOptions;
Icc: TBytes;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('directmail-merged.pdf');
Icc := LoadIccProfile('GRACoL2013_CRPC6.icc'); // your own loader
Opt := TPdfVTSaveOptions.Default;
Opt.Conformance := pvc1; // pvc2 is normalised to pvc1 on write
Opt.IccProfileData := Icc;
Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
Opt.OutputCondition := 'Commercial print, coated, CRPC6';
Opt.RegistryName := 'http://www.color.org';
Opt.Title := 'Spring 2026 Direct Mail Run';
Opt.Trapped := ptvFalse; // PDF/X Info /Trapped state
Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
finally
Pdf.Free;
end;
end;
إحدى التفاصيل في ذلك المقتطف ليست حدًا بل درعًا مقصودًا. تعيين Opt.Conformance := pvc2 لا ينتج ملف PDF/VT-2. فكاتب البيانات يطبّع أي طلب غير pvc1 إلى pvc1، لأن PDF/VT-2 صيغة مجموعة ملفات، وكاتب الملف الواحد الذي يلحق مستند إخراج واحدًا لا يستطيع ماديًا أن يجمع مجموعة الموارد الخارجية التي يطلبها §6.2.2. أما قيمة pvc2 فموجودة في مسار القراءة، حتى يستطيع ValidatePdfVT التعرف إلى ملف مجموعة ملفات موجود والإبلاغ عنه؛ إنها ليست هدف كتابة
شجرة DPart: هيكل يقرأه RIP فعلًا
قلب PDF/VT هو تسلسل Document Part (DPart). وهو ما يتيح للمطبعة تقسيم دفعة طويلة إلى سجلات، وجمع السجلات في متلقين أو حزم بريد، وإرفاق Document Part Metadata حتى يتمكن المعدّات اللاحقة من توجيه كل قطعة وفوترة كل قطعة. يرسم ISO 16612-2 §6.5 الأسلاك: يحمل الكتالوج /DPartRoot، ويحمل جذر عقدة DPart /DPartRootNode و/NodeNameList تسمّي كل مستوى من مستويات التسلسل الهرمي، وتغطي DParts الورقية النطاقات من شجرة الصفحات، وكل صفحة تنتمي إلى جزء تشير إلى أوراقها عبر إدخال /DPart على مستوى الصفحة
عندما يحتوي المستند المصدر أصلًا على تسلسل هرمي صالح، SaveAsPdfVTيحافظ عليه. وعندما لا يحتويه، ينشئ الكاتب واحدًا أدنى: عقدة DPart واحدة على مستوى المستند تمتد على شجرة الصفحات الحالية بالترتيب، مع /DPartمرجع خلفي يُلحَق بكل كائن صفحة حي وطبقة واحدة من /NodeNameList [/Document]. كن صريحًا مع نفسك بشأن ماهية تلك الشجرة الدنيا. إنها مرساة بنيوية تفي بمتطلبات الشكل في §6.5؛ وليست بيانات عمل. لا تستطيع اختراع المستلمين أو حدود قطع البريد أو دفعات المنتجات، لأن تلك المعلومات لم تكن موجودة أصلًا في المصدر. إذا كانت لديك بيانات لكل مستلم، فمن المتوقع أن تنشئ شجرة DPart أعمق بنفسك وتوسّع /NodeNameList ليطابق المستويات التي تنشئها
التحقق الذي يتجاوز مجرد وجود المفاتيح
ValidatePdfVTيعيد TPdfVTValidationResult سجلًا يحتوي على ثلاثة أشياء: Conformance المكتشفة، ومجموعة من Issues، وIsCompliant مساعد لا يعود true إلا عندما يكون التوافق مستوى حقيقيًا وتكون مجموعة المشكلات فارغة. تعداد المشكلات محدد عمدًا، لذلك يخبرك الفشل بأي بند فاتك بدلًا من مجرد "غير صالح":
var
Pdf: TPdf;
Res: TPdfVTValidationResult;
begin
Pdf := TPdf.Create(nil);
try
Pdf.LoadFromFile('statements-pdfvt.pdf');
Res := Pdf.ValidatePdfVT;
if Res.IsCompliant then
Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
else
begin
if pvviMissingDPartRoot in Res.Issues then
Writeln('DPart hierarchy missing or unusable');
if pvviMissingPdfXIdentifier in Res.Issues then
Writeln('PDF/X-4 base identifier absent');
if pvviMissingOutputIntent in Res.Issues then
Writeln('OutputIntent / ICC profile missing');
if pvviEncryptionPresent in Res.Issues then
Writeln('Encrypted - PDF/X forbids this');
end;
finally
Pdf.Free;
end;
end;
التحققان اللذان يستحقان الفهم بعمق هما إقران التوافق وجولة DPart، لأن كليهما كان متساهلًا أكثر من اللازم في السابق ثم شدد ليتوافق مع المواصفة. وعلى جانب الإقران، يجري المدقق مطابقة دقيقة، لا منطق "أي PDF/X يكفي": فملف PDF/VT-1 لا يُقبل إلا على أساس PDF/X-4، وملف PDF/VT-2 لا يُقبل إلا على PDF/X-4p، PDF/X-5g، أو PDF/X-5pg. أما وجود علامة PDF/VT-1 فوق أساس PDF/X-1a فيُبلغ عنه، لا يُمرر بصمت
جولة DPart هي موضع أغلب الصرامة. لا يكفي أن يحمل الكتالوج مفتاح /DPartRoot، لأن كائنًا فارغًا مزيفًا أو كائنًا بلا روابط صفحات لا يمكن استهلاكه أيضًا. HasValidDPartHierarchy وValidateDPartNode العودية تتتبعا الهيكل كله: تتبعان روابط الأب، وترفضان الأبناء المكررين والحلقات، وتفرضان أن /Start و/DParts متنافيان، وتطلبان أن تغطي نطاقات الصفحات الورقية شجرة الصفحات بترتيب عمق-أول مع كون /DPart لكل صفحة يشير إلى الورقة التي تحتويها. كل تلك الأعطال الداخلية تنضغط إلى بت المشكلة الواحد pvviMissingDPartRoot بدلًا من توسيع التعداد العام، لذلك عامِل تلك الراية الواحدة على أنها "تسلسل DPart الهرمي غير صالح للاستخدام"، لا حرفيًا "مفتاح الجذر مفقود"
ثلاث مصائد نحوية يفرضها المدقق الآن
كشفت تمريرات متعاقبة على §6.5 Table 4 عن أشكال كانت الإصدارات السابقة تقبلها بينما المعيار لا يفعل. وهذه من النوع الذي يخطئ فيه بناء شجرة DPart يدويًا، لذلك تستحق أن تُذكر صراحة:
/DPartsهي مصفوفة من مصفوفات، لا مصفوفة مسطحة.يجب أن يكون كل عنصر في المصفوفة الخارجية نفسه مصفوفة مرجعية غير مباشرة. مصفوفة/DParts [9 0 R]المسطحة تُرفض؛ والشكل المطابق هو/DParts [[9 0 R] [10 0 R]]. هذا يمنع بنية غير هرمية من التنكر على أنها مستوى صالح/Endفقط يوسم نطاقًا حقيقيًا متعدد الصفحات.قد يحمل DPart الورقي/Endفقط عندما يكون لديه أيضًا/Start، و/Endيجب أن يقع لاحقًا من/Startفي ترتيب شجرة الصفحات. أما/Start 3 0 R /End 3 0 Rالمتدهور فيجعل الهرمية غير صالحة للاستخدام بدل أن يُقرأ كجزء من صفحة واحدة/NodeNameListيجب أن تنجو أسماء بعد فك ترميز أسماء PDF لتبقى XML NMTOKENs./Bad#20Nameاسم مثل.،-،_،:، بالإضافة إلى بايتات غير ASCII) تلتقط أخطاء المسافات والفواصل دون رفض الأسماء المحلية أو أسماء المورّدين الصحيحة
علامات XMP: طريقتان لكتابة الخاصية نفسها
تعيش هوية PDF/VT في XMP تحت مساحة الاسم pdfvtid، وبالتحديد GTS_PDFVTVersion وGTS_PDFVTModDate، إلى جانب xmp:CreateDate وxmp:ModifyDate. ومن التفاصيل التي تسبب تقارير "مفقود" كاذبة في القارئات الساذجة أن أيًا من هذه القيم يمكن تسلسله بطريقتين: كنص عنصر (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) أو كصفة RDF على عنصر الوصف. يقرأ PDFiumPas الصيغتين، لذلك لا يُعاقَب الملف الذي كتبه أداة أخرى بأسلوب الصفات. كما يفرض قاعدة الاتساق في §6.3 التي تنص على أن GTS_PDFVTModDate يجب أن يساوي xmp:ModifyDate؛ وأي عدم تطابق يرفع pvviModDateMismatch
هناك قاعدة أخرى من الفقرة نفسها: أي قيمة غير معروفة لـ GTS_PDFVTVersion تُحفظ على أنها pvcUnknown بدل أن تُعاد إلى pvcNone. هذا الفرق مهم تشغيليًا. pvcNone يعني "لا توجد علامة PDF/VT أصلًا، مجرد PDF عادي"، بينما pvcUnknown يعني "شيء وسم نسخة لا يعرفها هذا المدقق" (ومنها حالة PDF/VT-2s). وخلط الاثنين سيخفي ملفًا معطوبًا داخل المربع نفسه الذي يوضع فيه مستند عادي
أين ينتهي الضمان
من المهم أن تكون دقيقًا بشأن حدود ما تعد به هذه الطرق، لأن امتثال طباعة البيانات المتغيرة له مال حقيقي مرتبط به. ففحوص DPart والإقران هي تحقق بنيوي على مستوى البايت. وهي تؤكد أن هيكل التحسين، وعلامات أساس PDF/X-4، وOutputIntent، وXMP موجودة ومتسقة داخليًا. لكنها ليست فحصًا مسبقًا لمحتوى PDF/X-4: فهي لا تتحقق من أن كل لون يقع داخل شرط الإخراج المعلن، ولا من أن جميع الخطوط مضمنة، ولا من أن أي حالة حدية محظورة لخلط الشفافية لم تتسلل. بالنسبة إلى مهمة تضعها على مطبعة تعاقدية، اقترن التحقق البنيوي في PDFiumPas بمحرك فحص مسبق PDF/X مخصص وبطباعة اختبارية، تمامًا كما تفعل مع أي ادعاء امتثال آخر. فطبقة البنية تلتقط الأعطال التي تكسر التخزين المؤقت في RIP بصمت؛ وهي نصف فحص كامل، لا كله
إذا كنت تبني هذه الفحوص داخل بوابة إصدار أوسع، فالمقاربة نفسها على مستوى البايت هي التي تقوم عليها أعمال المعيار الأخرى في المكتبة، بما في ذلك التحقق من تدفقات الكائنات وcross-reference قبل أن يصل ملف إلى مرحلة الفحص المسبق، وانضباط الكائنات المشتركة خلف طوابع الصفحات القابلة لإعادة الاستخدام مع Form XObjects الذي يجعل المستند صديقًا لـ RIP من الأصل. وتعد واجهات الحفظ والتحقق الخاصة بـ PDF/VT وPDF/X الموصوفة هنا جزءًا من مكوّن PDFium VCL لـ Delphi وC++Builder، وتحتوي صفحة المنتج على مرجع الامتثال الكامل