مقال تقني

استيراد SVG إلى PDF كعناصر Form XObject قابلة لإعادة الاستخدام

يستورد HotPDF ملف SVG إلى ملف PDF محمّل أو منشأ حديثًا كعنصر Form XObject قابل لإعادة الاستخدام عبر ImportSVGFormXObject، الذي يُعيد اسم المورد الخاص بالنموذج الذي تم إنشاؤه. أما وضعه على الصفحة فهو استدعاء منفصل، DrawSVGFormXObject، بحيث يمكن طبع استيراد واحد على صفحات عديدة بأحجام مختلفة دون تكرار ولو بايت واحد من الرسم

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

رسم تخطيطي لعملية استيراد SVG واحدة تنشئ عنصر Form XObject واحدًا في PDF تضعه DrawSVGFormXObject على صفحات عديدة بأحجام مختلفة، مقارنة بمئات النسخ من الصور النقطية
استيراد واحد يكتب الرسم مرة واحدة كعنصر Form XObject له موارده الخاصة، وكل عملية وضع هي استدعاء صغير لا يضيف أي بايتات مكررة

لماذا Form XObject بدلاً من رسم SVG في كل مرة؟

عنصر Form XObject هو آلية PDF الخاصة بالمحتوى القابل لإعادة الاستخدام: دفق محتوى مستقل بذاته له مربع إحاطة وقاموس موارد خاصان به، ويُستدعى بالاسم. استيراد SVG إلى عنصر من هذا النوع يمنحك ثلاث خصائص لا يوفرها الرسم المباشر. فالرسم يوجد مرة واحدة فقط في الملف. وكل عملية وضع هي مصفوفة صغيرة واستدعاء. وتبقى الأشكال متجهية، بحيث يرسم العارض عند تكبير 800 بالمئة منحنيات لا بكسلات

والدقة الحقيقية تكمن في الموارد. يجب أن تعيش خطوط النموذج وحالات الرسوميات الخاصة به في قاموس /Resources الخاص بالنموذج نفسه، لا في موارد أي صفحة كانت هي الصفحة الحالية وقت الاستيراد. يكتب HotPDF خطوط Base 14 ومدخلات ExtGState التي يحتاجها النموذج داخل النموذج نفسه، لأن نموذجًا يستعير اسمًا من صفحته المضيفة الأولى يُعرض بشكل صحيح فيها فقط وينتج اسم مورد غير محلول في كل صفحة أخرى يُوضع عليها لاحقًا

قلبان، واحد للهندسة وآخر للحروف

ينمو نظام إحداثيات SVG نحو الأسفل بدءًا من الزاوية العلوية اليسرى؛ بينما يستخدم عنصر Form XObject في PDF فضاء المستخدم الخاص بـ PDF، الذي ينمو نحو الأعلى بدءًا من الزاوية السفلية اليسرى. يُنفّذ تحويل viewBox الجذري قلبًا واحدًا على المحور Y بحيث تستقر الهندسة في مكانها الصحيح. ثم يحتاج النص إلى قلب ثانٍ محلي، لأن أي حرف مرسوم داخل إطار مقلوب أصلاً سيظهر مقلوبًا رأسًا على عقب

رسم تخطيطي للقلبين على المحور Y بين إحداثيات SVG وPDF: تحويل viewBox الجذري يصحح الهندسة، وتحتاج حروف النص إلى قلب محلي ثانٍ لتبقى منتصبة
يقلب تحويل viewBox الجذري المحور Y مرة واحدة بحيث تستقر الهندسة في فضاء PDF، ويأخذ النص قلبًا محليًا ثانيًا وإلا وصل كل حرف مقلوبًا رأسًا على عقب

وهذا هو نفس التصحيح الذي يظهر بشكل معكوس عند كتابة صفحة PDF كملف SVG، كما هو موضح في تصدير صفحات PDF إلى SVG. والخطأ في هذا القلب سهل الملاحظة في الرسوم التي تحتوي نصًا، وشبه غير مرئي في الرسوم الخالية منه، ولهذا يُعدّ شعار مصحوب بشعار فرعي نصي اختبارًا أوليًا أفضل من شعار وحده

uses
  HPDFDoc;

var
  Pdf: THotPDF;
  SvgBytes: TBytes;
  FormName: AnsiString;
  Stats: THPDFSVGImportStatistics;
  I: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := 'catalogue.pdf';
    Pdf.BeginDoc;

    SvgBytes := LoadFileBytes('brand-logo.svg');
    FormName := Pdf.ImportSVGFormXObject(SvgBytes, Stats);
    if FormName = '' then
      raise Exception.Create('SVG rejected - see the import statistics');

    // استيراد واحد، وضع في أماكن عديدة: بايتات الرسم موجودة مرة واحدة فقط
    for I := 1 to 40 do
    begin
      if I > 1 then
        Pdf.AddPage;
      Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 780, 120, 40);
    end;

    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;
end;

كيف تنجو الروابط التشعبية من عملية النقل؟

لا يمكن لهذه الروابط أن تنجو داخل النموذج، لأن عنصر Form XObject هو محتوى بينما أهداف الروابط في PDF هي حواشي توضيحية (annotations) تنتمي إلى صفحة. لذلك لا يمكن ببساطة نسخ عنصر <a> الموجود في مصدر SVG إلى دفق محتوى النموذج

يحل HotPDF هذه المشكلة بالاحتفاظ بمستطيلات الروابط كبيانات وصفية (metadata) في فضاء النموذج أثناء التحليل، ثم تجسيدها كحواشي روابط على مستوى الصفحة عند كل عملية وضع. تقوم DrawSVGFormXObject بتحويل تلك المستطيلات عبر المستطيل الهدف الذي تمرره، بحيث يحصل نموذج موضوع مرتين بحجمين مختلفين على مجموعتين من مناطق النقر بالحجم الصحيح. تُحوَّل مستطيلات الروابط عبر حجم الجذر فقط ولا تدخل أبدًا في دفق المحتوى، مما يُبقي الرسم المرسوم مطابقًا بغض النظر عن عدد الروابط التي يحملها. مرّر AddLinks = False عندما تريد الرسم دون التفاعلية، على سبيل المثال في صفحة مخصصة للطباعة فقط

ما الذي يرفضه المستورِد، ولماذا هذا هو الهدف بالضبط

لغة SVG هي لهجة من XML تدعم البرمجة النصية والمراجع الخارجية وتوسيع الكيانات (entity expansion)، مما يجعل أي ملف SVG عشوائي مدخلاً معاديًا محتملاً تمامًا كما هو الحال مع أي ملف HTML عشوائي. يعتمد ملف تعريف الاستيراد سياسة "الفشل المغلق" (fail-closed) تجاه كل ما يمتد خارج المستند: المراجع إلى موارد خارجية، والبرمجيات النصية، وسمات معالجات الأحداث، وتعريفات DTD، وأي url() ليس مرجعًا محليًا لجزء داخل المستند

يعني الفشل المغلق أن عملية الاستيراد تفشل بدلاً من إسقاط البنية المخالفة بصمت. هذا الفرق مهم في خطوط المعالجة الدُفعية (batch pipeline)، لأن الإسقاط الصامت ينتج مستندًا ناقصًا شيئًا لا يعلم به أحد، بينما الرفض ينتج ملفًا يمكنك عزله وفحصه. إذا كنت تقبل رفع ملفات SVG من المستخدمين، فهذا هو السلوك الذي تريده، وهو يتكامل مع العزل على مستوى العمليات الموصوف في عزل عمليات محوّلات الترميز للصور غير الموثوقة

رسم تخطيطي لملف تعريف استيراد SVG بسياسة الفشل المغلق يقبل ميزات المتجهات المحلية ويرفض البرمجيات النصية والمراجع الخارجية وتعريفات DTD بإفشال عملية الاستيراد
يرفض ملف تعريف الاستيراد البرمجيات النصية والمراجع الخارجية وتعريفات DTD بالفشل الصريح، مما يمنحك ملفًا يمكن عزله بدلاً من مستند ناقص بصمت

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

التدرجات اللونية، الأقنعة، وقاعدة عدم الترسيب النقطي

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

والمقايضة هي الدقة في الحالات القصوى. فالتقريب المُقسَّم لشرائط لتدرج ناعم ليس مطابقًا رياضيًا للتدرج الأصلي في SVG، والرسوم التي تحتوي عشرات الأقنعة الناعمة المتداخلة ستبدو أقرب إلى الأصل إذا رسمتها كصورة عالية الدقة بدلاً من ذلك. للاطلاع على نماذج التظليل التي يدعمها PDF أصلاً، ومتى تلجأ إليها مباشرة، راجع رسم التظليلات المحورية والشعاعية

// قراءة إحصاءات الاستيراد تُظهر لك ما تم استيراده فعليًا
Writeln(Format('nodes=%d css rules=%d gradients=%d masks=%d',
  [Stats.NodeCount, Stats.CSSRuleCount, Stats.GradientCount,
   Stats.MaskCount]));
Writeln(Format('text runs=%d links=%d form bytes=%d',
  [Stats.TextRunCount, Stats.LinkCount, Stats.OutputBytes]));

// وضع النموذج نفسه بحجمين ينتج مجموعتي روابط صحيحتين
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 700, 240, 80);
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 320, 700, 120, 40);

// وضع مخصص للطباعة فقط: رسم دون مناطق النقر
Pdf.CurrentPage.DrawSVGFormXObject(FormName, 40, 60, 120, 40, False);

أين يقع استيراد SVG بين مسارات المتجهات الأخرى

توجد ثلاثة مسارات لاستيراد المتجهات، وهي غير قابلة للتبادل فيما بينها. استيراد SVG هو الخيار الصحيح للرسوم الناتجة عن أدوات التصميم ولأي شيء يصل من خط معالجة ويب. أما استيراد الملفات الوصفية (metafile)، الموضح في استيراد متجهات EMF و WMF، فهو الخيار الصحيح للمحتوى الذي تولّده تطبيقات Windows ومكونات الرسوم البيانية التي ترسم إلى سياق جهاز. أما الرسم المباشر عبر واجهة برمجة الصفحة فهو الخيار الصحيح للرسوم التي يحسبها كودك الخاص، حيث لا توجد أي صيغة وسيطة على الإطلاق

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

يشترك استيراد SVG واستيراد الملفات الوصفية والرسم المتجهي المباشر في كاتب دفق المحتوى نفسه الخاص بـ Delphi و C++Builder؛ وتجد قائمة الميزات الكاملة على صفحة مكوّن HotPDF لـ PDF في Delphi