مقال تقني

إخراج PDF/X-1a و X-3 و X-4 للطباعة في Delphi عبر PDFlibPas

ينتج PDFlibPas ملفات طباعة PDF/X من Delphi باستدعاء واحد هو TPDFlib.SetPDFXMode، والذي يختار PDF/X-1a:2003 أو PDF/X-3:2003 أو PDF/X-4. يكتب الاستدعاء تعريف XMP بصيغة pdfxid، ويضيف نية إخراج (output intent) من نوع GTS_PDFX، ويعلن /Trapped /False، ويثبّت نسخة PDF، ومن تلك اللحظة يرفض استدعاءات الرسم التي يحظرها الجزء المختار. أما CheckFileCompliance مع الاختبار 3 فتراجع الملف المحفوظ

هذه هي الإجابة المختصرة. والإجابة الأطول مهمة لأن مطابع الطباعة لا ترفض الملفات لقبحها، بل ترفضها في الفحص المسبق (preflight)، غالباً يوم جمعة، وغالباً بسبب كان باستطاعة كود التوليد أن يمسكه: شعار RGB في مهمة X-1a، أو خط أُشير إليه بدل تضمينه، أو صفحة بلا مقاس قصّ (trim size). وُجد PDF/X ليجعل تلك الإخفاقات بنيوية لا بصرية، وجانب المكتبة من المهمة هو أن يمنعك من إنتاجها أصلاً

أي جزء من PDF/X يستهدفه تطبيق Delphi؟

اختر PDF/X-4 ما لم يقل سير العمل المستقبِل غير ذلك، واختر PDF/X-1a حين يقوله. الأجزاء الثلاثة تختلف على ثلاثة محاور بالضبط: اللون، والشفافية، ونسخة PDF الأساس، وكل محور يقابل بوابة داخل PDFlibPas:

  • PDF/X-1a:2003 (ISO 15930-4): صياغة PDF 1.4، وألوان CMYK ورمادية وSpot فقط، بلا ألوان قائمة على ICC وبلا شفافية. الملف مفصول مسبقاً إلى ألوان المطبعة، فلا تجري الطابعة أي تحويل لوني إطلاقاً
  • PDF/X-3:2003 (ISO 15930-6): لا يزال PDF 1.4 وما زال بلا شفافية، لكن مساحات الألوان المُدارة لونياً مسموح بها، ويُقبل RGB الجهازي عندما تصف نية الإخراج نفسها حالة RGB
  • PDF/X-4 (ISO 15930-7): صياغة PDF 1.6، مع شفافية حية ومحتوى اختياري (طبقات). هذا هو الجزء الذي بُنيت له أنظمة RIP الحديثة، لأن تسطيح الشفافية على المخرج هو مصدر معظم آلام X-1a القديمة

X-1a ما يزال حاضراً في كل مكان في تسليم الإعلانات وفي سير عمل الطباعة الأقدم الذي يريد ملفاً يمكن تصويره دون تفكير. و X-4 هو الخيار الهندسي الأفضل كلما قبِله المطبع: الشفافية تنجو سالمة، وقرارات اللون تحدث عند الـ RIP مع ملف تعريف المطبعة الحقيقي بدلاً من أن تحدث في كودك. أرقام الأنماط (mode) هي 1 و2 و3 على الترتيب، و GetInformation(203) يبلّغ عن النمط النشط. ويعرّف PDFlibPas أيضاً أرقام أنماط أعلى لعائلة PDF/X-6 على PDF 2.0، وهي تتبع قواعد نسخة ونية إخراج مختلفة وتخرج عن نطاق هذه المقالة

ماذا يكتب SetPDFXMode في الملف

TPDFlib.SetPDFXMode يتولى التعريف والقالب الثابت (boilerplate) الذي يفحصه قارئ PDF/X أولاً، بحيث لا يضطر كود التطبيق إلا لتوفير ملف تعريف المطبعة الحقيقي والمحتوى. وعلى نحو ملموس، الاستدعاء الناجح يفعل أربعة أشياء:

  • يكتب سلسلة الإصدار في XMP بصيغة pdfxid:GTS_PDFXVersion (PDF/X-1a:2003 أو PDF/X-3:2003 أو PDF/X-4). إصدارات 2003 تحدد الجزء تمييزاً كاملاً من خلال تلك السلسلة، فلا تُكتب خاصية مطابقة منفصلة
  • يضيف نية إخراج إن لم يملك المستند واحدة ويضبط نوعها الفرعي إلى /S /GTS_PDFX، النوع الفرعي الذي تبحث عنه قارئات PDF/X (قاموس نية الإخراج معرّف في ISO 32000-1 §14.11.5)
  • يضبط /Trapped /False في قاموس معلومات المستند، لأن الكاتب لا يجري trapping أبداً و PDF/X يشترط إعلان حالة الـ trapping بدلاً من تركها مجهولة
  • يثبّت نسخة الأساس: 1.4 لـ X-1a و X-3، و1.6 لـ X-4

إصلاحان إضافيان يحدثان عند الحفظ. كل صفحة بلا TrimBox تحصل على واحد يساوي MediaBox الخاص بها، لأن PDF/X يشترط مقاساً نهائياً معلناً على كل صفحة (ISO 32000-1 §14.11.2 يغطي حدود الصفحة)، وتحصل التعليقات التوضيحية على علم الطباعة الذي يتوقعه المعيار. تراجع الـ TrimBox شبكة أمان، لا قرار تخطيط. المهمة كاملة الفرش (full-bleed) تحتاج صندوق قصّ حقيقياً داخل صندوق وسائط أكبر، ودليل قيم TrimBox و BleedBox و CropBox الافتراضية في PDFlibPas يشرح كيفية ضبط تلك الصناديق وكيف يمكن للوراثة أن تفاجئك

uses
  PDFlibrary;

procedure BuildX1aAd(const OutFile, PressProfile: string);
var
  Pdf: TPDFlib;
begin
  Pdf := TPDFlib.Create;
  try
    if Pdf.SetPDFXMode(1) <> 1 then          // 1 = PDF/X-1a:2003
      raise Exception.CreateFmt('PDF/X mode refused (error %d)',
        [Pdf.LastErrorCode]);

    // استبدل الملف التعريفي الافتراضي بظروف المطبعة الحقيقية
    if Pdf.LoadOutputIntentProfile(PressProfile, 'DeviceCMYK') <> 1 then
      raise Exception.Create('Could not load the CMYK output profile');

    // Embed = 1 يضمّن برنامج الخط الكامل؛ PDF/X لا يقبل 0
    if Pdf.AddTrueTypeFont('Arial {1252}', 1) = 0 then
      raise Exception.Create('Font could not be loaded for embedding');

    Pdf.SetTextSize(24);
    Pdf.SetFillColorCMYK(0, 1, 1, 0);           // أحمر طباعي، بلا RGB
    Pdf.DrawText(72, 720, 'Spring catalog, page 1');

    Pdf.SaveToFile(OutFile);
  finally
    Pdf.Free;
  end;
end;

الترتيب في ذلك المسودة مقصود. LoadOutputIntentProfile يستبدل ملف تعريف نية إخراج موجودة أصلاً، لذا لا بد أن يعمل بعد أن يكون SetPDFXMode قد أنشأ واحدة. والاستدعاء أيضاً لا يستطيع ابتداع نية من العدم: على مستند بلا نية إخراج يعيد 0 ببساطة

لماذا تحتاج نية الإخراج الافتراضية إلى الاستبدال؟

نية الإخراج التي يضيفها SetPDFXMode للأنماط 1 إلى 3 تعلن DeviceCMYK، لكن بايتات ICC خلفها هي ملف sRGB المرفق مع المكتبة وقد أُعيدت كتابة عدد مكوناته إلى 4. ذلك الافتراضي موجود من أجل التوافق مع الإصدارات السابقة وهو كافٍ لجعل الملف بنيةً PDF/X، لكن الملف التعريفي المضمّن لا يصف أي مطبعة. أداة فحص مسبق تتفحص ترويسة الملف التعريفي سترى ملف RGB يرتدي ملصق CMYK، والطابع الذي يعتمد على النية في البروفة (proofing) يحصل على هراء

لذلك عامل LoadOutputIntentProfile(FileName, 'DeviceCMYK') بوصفه إلزامياً لعمل الطباعة الحقيقي، ومرّر ملف ICC الذي يسميه مزوّد الطباعة لديك للورقة والمطبعة. TPDFlib.LoadOutputIntentProfile يستبدل تدفق DestOutputProfile ويحدّث /N، ويترك بقية قاموس النية كما كُتب. وعلى وجه الخصوص، معرّف حالة الإخراج والسلاسل الوصفية للنية الافتراضية ما زالا يحملان قيماً بأسلوب sRGB، فإذا كانت مطبعتك تطابق على المعرّف بدل الملف التعريفي المضمّن، أكّد معها ماذا تفحص قبل أن تشحن. تلك فجوة حقيقية في الافتراضيات من عصر 2003، وليست شيئاً أخطأ فيه كودك

المزلة الأولى: النسخة التي لم تخترها

مستند PDFlibPas جديد يبدأ من الافتراضي الكاتب وهو PDF 1.5، وهو أعلى من أساس 1.4 الذي يتطلبه PDF/X-1a و PDF/X-3. TPDFlib.SetPDFXMode يحل ذلك بصمت في الحالة الشائعة: إذا لم تكن نسخة الحفظ مقفلة، يخفض نسخة المستند إلى أساس الجزء ويواصل. التنفيذ الأول كان يرفض بدلاً من ذلك، وكان كل SetPDFXMode(1) على مستند جديد يعيد 0، وهو مثال جيد على قاعدة صحيحة على الورق وعديمة الفائدة عملياً

الرفض ما يزال موجوداً للحالة الوحيدة التي يكون فيها صائباً. إذا استدعى شيءٌ ما سابقاً LockSaveVersion بنسخة أعلى من الأساس، يعيد SetPDFXMode قيمة 0 مع LastErrorCode بقيمة 601، لأن المستدعي قفل النسخة عن قصد والمكتبة لن تطأ ذلك. وفي كود إعداد مستند مشترك، حيث يقفل مساعدٌ النسخة لأسباب لا علاقة لها بالطباعة، هنا بالضبط تموت مهمة X-1a:

Pdf := TPDFlib.Create;
try
  Pdf.LockSaveVersion('1.7');     // ضبطه مساعد عام لـ "house defaults"
  if Pdf.SetPDFXMode(1) = 0 then
    // LastErrorCode = 601: الإصدار 1.7 مقفل، و PDF/X-1a يحتاج 1.4.
    // تجاوز القفل في مهام الطباعة. PDF/X-4 ليس مخرجاً هنا:
    // أساسه 1.6 أدنى من قفل 1.7 أيضاً ويفشل بالطريقة نفسها.
    LogError('PDF/X refused, error %d', [Pdf.LastErrorCode]);
finally
  Pdf.Free;
end;

البوابة نفسها تشرح قيداً ذا صلة. نمط PDF/X مفتاح إنشاء مستند: يعمل SetPDFXMode على مستند جديد ويعيد 0 على مستند حُمّل من ملف، لأن المكتبة لا تستطيع أن تضمن محتوى لم تكتبه. تحويل PDF موجود إلى PDF/X مهمة مختلفة تحتاج فحصاً وإصلاحاً، لا علماً (flag)

المزلة الثانية: بوابات ترفض بصمت

متى صار نمط PDF/X نشطاً، يرفض PDFlibPas كل استدعاء محتوى يحظره الجزء المختار، في اللحظة التي تجري فيها الاستدعاء، بدلاً من أن يترك الملف يفشل في الفحص المسبق لاحقاً. الرفوض تشمل تعبئات وحدود RGB الجهازية (كل X-1a؛ و X-3 و X-4 ما لم تكن نية الإخراج RGB)، والألوان القائمة على ICC في X-1a، والشفافية وأنماط الدمج في X-1a و X-3، والمحتوى الاختياري في X-1a و X-3، وأفعال JavaScript، ومرفقات الملفات المضمّنة، ومرشح الصور LZW، وخطوط Standard 14 التي ستذهب بلا تضمين، وتشفير المفتاح العام. تشفير كلمة المرور عبر Encrypt لا يمر ببوابة وقت الإنشاء، لكن المدقق يعلّم أي ملف مشفر، فأبقِ مهام الطباعة غير مشفرة

ما لا تتشاركه الرفوض هو كود الخطأ، وهذه هي الفخ. بوابات فضاء الألوان في SetFillColor و SetLineColor والاستدعاءات القائمة على ICC و EncryptPublicKey تضبط LastErrorCode على 601، كود مطابقة المكتبة الذي تستخدمه بوابات PDF/A أيضاً. أما بوابات الشفافية و JavaScript والمرفقات و LZW و AddStandardFont فتعيد 0 ببساطة وتترك LastErrorCode كما هو، ما يعني أنه قد لا يزال يحمل قيمة من استدعاء سابق. الكود الذي يفحص LastErrorCode وحده بعد دفعة استدعاءات رسم سيفوّت نصف الرفوض، فافحص قيمة إرجاع كل استدعاء:

procedure Require(Ok: Integer; const What: string);
begin
  if Ok = 0 then
    raise EPrintConformance.CreateFmt('%s refused under PDF/X', [What]);
end;

Require(Pdf.SetFillColorCMYK(0, 0, 0, 1), 'CMYK fill');
Require(Pdf.SetTransparency(40), 'transparency');   // 0 في X-1a / X-3
Require(Pdf.AddLinkToJavaScript(72, 72, 120, 20,
  'app.alert(1)', 0), 'JavaScript link');           // 0 في كل جزء

الخطوط تستحق تحذيراً خاصاً بها. AddStandardFont يعيد 0 تحت PDF/X لأن المكتبة لا ترفق مخططات Standard 14 فلا تستطيع تضمينها. أما AddTrueTypeFont فمختلف: في نمط PDF/A يرقّي Embed = 0 إلى تضمين كامل نيابة عنك، لكن في نمط PDF/X لا يفعل، فيدخل خط النظام المُشار إليه في الملف ولا يلتقطه إلا المدقق. مرّر 1 (كامل) أو 2 (جزئي) بنفسك، أو استدعِ SetEmbedAllFonts(1) مرة واحدة عند الإعداد حتى لا يتسلل 0 طائشاً

كيف تتحقق من ملف PDF/X قبل ذهابه إلى المطبعة؟

شغّل CheckFileCompliance بـ ComplianceTest = 3 على الملف المحفوظ. يعيد TPDFlib.CheckFileCompliance قيمة 0 حين لا يسجل أي نتائج، وإلا فمقبض قائمة نصوص بنودها أسطر مسبوقة بـ PDFX-. يقرأ تدقيق PDF/X الملف عبر قارئ التدفق نفسه المستخدم في الفحص المسبق لـ PDF/A و PDF/UA في PDFlibPas، ويفحص القواعد التي يفحصها فحص طباعة مسبق أولاً:

  • الملف غير مشفر، ويحمل XMP قيمة GTS_PDFXVersion معروفة، ونسخة PDF لا تتجاوز أساس الجزء
  • نية إخراج واحدة بالضبط موجودة، ونوعها الفرعي GTS_PDFX، وتحمل ملف تعريف وجهة (destination profile) ومعرّف حالة إخراج
  • قاموس المعلومات يعلن /Trapped
  • كل صفحة لديها TrimBox يقع داخل MediaBox، و BleedBox إن وُجد يحيط بالـ TrimBox
  • كل خط مستخدم على صفحة لديه برنامج مضمّن، مع فحص خطوط Type0 عبر خطها السليل (descendant font)
  • لا أنواع تعليقات توضيحية محظورة (RichMedia و Screen و 3D و Movie و Sound و FileAttachment)، ولا فتح JavaScript أو JavaScript على مستوى المستند، ولا ملفات مضمّنة، ولا مرشح LZWDecode في أي مكان في مخطط الكائنات
function PreflightPDFX(Pdf: TPDFlib; const FileName: string;
  Report: TStrings): Boolean;
var
  ListId, I: Integer;
begin
  ListId := Pdf.CheckFileCompliance(FileName, '', 3, 0);  // 3 = PDF/X
  Result := ListId = 0;
  if Result then
    Exit;
  try
    for I := 1 to Pdf.GetStringListCount(ListId) do       // العناصر تبدأ من 1
      Report.Add(Pdf.GetStringListItem(ListId, I));
  finally
    Pdf.ReleaseStringList(ListId);
  end;
end;

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

الأسئلة الشائعة

هل يدعم PDFlibPas شفافية PDF/X-4؟

نعم. في SetPDFXMode(3) يسمح PDFlibPas بالشفافية وأنماط الدمج ومجموعات المحتوى الاختياري ويكتب ملف PDF 1.6. الاستدعاءات نفسها تعيد 0 تحت PDF/X-1a و PDF/X-3

هل يمكن لـ PDFlibPas تحويل PDF موجود إلى PDF/X؟

لا. ينطبق SetPDFXMode على المستندات المنشأة في جلسة TPDFlib الحالية فقط ويعيد 0 لملف محمّل. لا يزال بإمكانك تدقيق أي PDF موجود عبر CheckFileCompliance والاختبار 3

ما كود الخطأ الذي تعيد به استدعاء PDF/X مرفوض؟

رفوض فضاء الألوان وتشفير المفتاح العام تضبط LastErrorCode على 601. بوابات أخرى، مثل الشفافية و JavaScript وخطوط Standard 14، تعيد 0 فقط، فافحص دائماً نتيجة الدالة

إخراج PDF/X ومدقق PDF/X وبقية أدوات الطباعة والأرشفة جزء من PDFlibPas، مكتبة losLab PDF الأصلية بلغة Object Pascal لـ Delphi. راجع صفحة منتج مكتبة PDFlibPas لـ Delphi للإصدارات والمنصات ومرجع API الكامل