مقال تقني

خطوط الويب في PDF: WOFF2 والمحاور المتغيرة في Delphi

تقبل PDF Library for Delphi الخطوط التي يشحنها مشروع ويب بالفعل. فهي تستورد حاويات WOFF2، وتُعدِّد محاور التصميم لخط متغير كـ JSON، وتُجسِّد مجموعة مختارة من إحداثيات المحاور إلى خط ثابت تتعامل معه مسارات التضمين والتجزئة الفرعية والاستخراج العادية دون معرفة أي شيء عن التغيرات. وكل من مخططات TrueType ومخططات CFF2 مغطاة

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

لماذا لا يكون WOFF2 مجرد خط مضغوط؟

معاملة WOFF2 كغلاف Brotli وفك ضغطه يمنحك بايتات ليست sfnt صالحًا. فالصيغة تسمح أيضًا بجداول مُحوَّلة ومُعاد ترتيبها: يمكن تخزين glyf و loca و hmtx بشكل مُعاد ترتيبه يجب عكسه قبل أن يصبح الخط قابلاً للتحليل. وأداة فك ضغط عامة الغرض لا تستطيع فعل ذلك

وعلى Windows، توفّر المنصة تحليل الحاوية وفك حزمها منذ Windows 10 الإصدار 1703، لذا تستخدم المكتبة تنفيذ النظام بدلاً من شحن مفكِّك ترميز آخر. ويُبقي هذا القرار النشر خاليًا من DLL إضافي ويعني أن عكس التحويل تتولاه الشيفرة نفسها التي تستخدمها حزمة النصوص الخاصة بنظام التشغيل. وعلى منصات أخرى، لا يزال WOFF1 يسلك مسار Pascal صرفًا بينما يُبلَّغ عن WOFF2 صراحة كغير مدعوم، وهذه إجابة صادقة بدلاً من خط تالف بصمت

ومسار الاستيراد يتحقق قبل فك الترميز وبعده: تُفحص الترويسة البالغة 48 بايت، وطول الحاوية، والحقول المحجوزة، وحجم sfnt المُعلَن، وسقف 512 ميبيبايت أولاً، ويُقارَن حجم الدفق المفكوك ترميزه بالإعلان بعد ذلك. وخط الويب محتوى مُنزَّل، ونمطا الفشل الجديران بالحظر هما عدم تطابق الحجم وقنبلة فك الضغط

يُظهر رسم خط الأنابيب هذا كيف تستورد مكتبة PDF لـ Delphi خط ويب من نوع WOFF2 عبر التحقق من الترويسة، وفك الترميز عبر النظام، وعكس تحويل sfnt، وتدقيق الحجم بعد فك الترميز
تصل حاويات WOFF2 كمحتوى مُنزَّل، لذلك يتحقق الاستيراد من الترويسة والأحجام المُعلَنة قبل فك الترميز وبعده
uses
  PDFlibrary;

var
  Lib: TPDFlib;
  FontID, InstanceID: Integer;
  AxesJSON: WideString;
begin
  Lib := TPDFlib.Create;
  try
    Lib.NewDocument;
    Lib.SetPageSize('A4');
    Lib.NewPage;

    // WOFF2 و WOFF1 و TTF و OTF و TTC كلها تدخل عبر الاستدعاء نفسه
    FontID := Lib.AddOpenTypeFontFromFile('Inter.woff2', 0);
    if FontID <= 0 then
      raise Exception.Create('font could not be imported');

    // ما الذي يستطيع هذا الخط فعله فعليًا؟
    if Lib.GetFontVariationAxes(FontID, AxesJSON) = 1 then
      Log(AxesJSON);   // العلامات، والحد الأدنى، والافتراضي، والحد الأقصى لكل محور

    // اخبز موضع تصميم واحد في خط ثابت
    InstanceID := Lib.CreateOpenTypeFontVariation(FontID,
      'wght=620,wdth=87.5');

    Lib.DrawOpenTypeText(56, 760, InstanceID, 24, 'Quarterly results', 0);
    Lib.SaveToFile('report.pdf');
  finally
    Lib.Free;
  end;
end;

ما الذي يجب أن يعيد التجسيد بناءه، وما الذي يزيله

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

وبالنسبة لمخططات TrueType، يعني ذلك تحليل fvar و avar و gvar و HVAR و MVAR، وتطبيق فروق الصفوف الرقمية بترقيم نقاط مشترك وخاص، واستيفاء النقاط التي لا تحمل فرقًا صريحًا. وتحدد النقاط الوهمية الأفقية مع HVAR عرض التقدّم والمسافة الجانبية اليسرى. وتحتاج الحروف المركّبة إلى حل اعتماديات مكوّناتها وتخطيطات نقاطها أولاً، ثم تطبيق إزاحات النقل والتحجيم والمصفوفة والإرفاق — بحدود عمق وكشف دورات، لأن رسمًا بيانيًا خبيثًا للمكوّنات كان سيتكرر بلا نهاية لولا ذلك

وتتحرك البيانات الوصفية أيضًا، وهذا الجزء الذي تتجاهله معظم التنفيذات. تحدّث MVAR المقاييس العامة في hhea و OS/2 و post، بحيث يطابق تباعد الأسطر وموضع التسطير وزاوية الميل التجسيد لا الافتراضي. وتحدّث محاور wght و wdth و ital و slnt بالإضافة إلى ذلك فئة الوزن، وفئة العرض، وأعلام الاختيار، و macStyle، وزاوية الميل، بحيث تُبلّغ أداة تفحص الخط الثابت عن النمط الذي طلبته. وخط هندسته عريضة وبياناته الوصفية تقول عادي سيُوسَم بشكل خاطئ في كل مكان لاحق

ثم تُسطِّح المخرجات المخططات المركّبة وتزيل الجداول التي لم تعد تنطبق: جداول التغيير، والتلميحات، وجداول الأجهزة، وأي توقيع. ويُعاد حساب حدود الحروف، ومقاييس ملخص hhea، وسقوف تلميحات maxp، ومجاميع اختبار كل جدول، ومجموع اختبار الخط بأكمله

يُظهر هذا الرسم التخطيطي كيف يعيد تجسيد الخط المتغير بناء وجه TrueType باستيفاء فروق gvar، وتوسيع الحروف المركّبة، وإعادة حساب التقدّمات، وإعادة كتابة البيانات الوصفية للنمط قبل إزالة جداول التغيير
يعيد التجسيد بناء كل ما لمسته الفروق، ثم يزيل آلية التغيير بحيث يرى التضمين العادي خطًا ثابتًا عاديًا

CFF2 يسلك طريقًا مختلفًا إلى المكان نفسه

لا يمكن تضمين CFF2 كبرنامج خط PDF مباشرة: فسلاسل أحرفه لا تحمل عروضًا، وهو يعتمد على جداول OpenType المحيطة به لترتيب الحروف والمقاييس. لذلك ينتج تجسيد خط CFF2 خط OpenType من نوع CFF مفهرَس بـ CID بدلاً من ذلك، وهو ما تتعامل معه مسارات الرسم والتضمين والتجزئة الفرعية الموجودة بالفعل

وتحميل خط CFF2 ينتج فورًا تجسيدًا ثابتًا بإحداثيات افتراضية للمستند الحالي، بينما يُحتفَظ بـ sfnt الأصلي من نوع CFF2 على كائن الخط بحيث لا يزال تعداد المحاور والتجسيد اللاحق يريان فضاء التصميم الكامل. ويُنزَع كل حرف من روتينه الفرعي بشكل فردي أثناء التجسيد، مما يزيل حسابيات التغيير كليًا ويترك المُجزِّئ الفرعي الموجود يعمل دون تغيير. والمقايضة هي أن التجسيد الثابت قد يكون أكبر قليلاً من تجسيد احتفظ بروتينات فرعية مشتركة — ثمن عادل مقابل عدم الحاجة إلى تنفيذ تجزئة فرعية ثانٍ

التحقق من تجسيد قبل أن تثق به

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

صدّر التجسيد وافتحه في أداة خطوط. تكتب SaveFontToFile برنامج الخط المُحتفَظ به، بحيث يمكن تفريغ تجسيد في الذاكرة دون إنتاج PDF أولاً. ويجب أن يُحلَّل الملف المُصدَّر بنظافة، ولا يحتوي أي جداول تغيير، ويُبلّغ عن البيانات الوصفية للنمط التي طلبتها

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

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

يُظهر هذا الرسم التخطيطي ثلاثة فحوصات تحقق لتجسيد خط متغير مُولَّد: تفريغ ملف الخط المُصدَّر، ومقارنة تقدّمات الحروف مقابل تجسيد مستقل، واستخراج النص مرة أخرى من ملف PDF
تجسيد خاطئ بشكل دقيق لا يطرح أي استثناء، لذلك يُتحقق من الملف المُصدَّر، وتقدّمات الحروف، والنص المُستخرَج، كل على حدة بالتتابع
// فرّغ تجسيدًا مُولَّدًا للفحص أو إعادة الاستخدام
Lib.SaveFontToFile('Inter-620-87.ttf');

// اطلب إحداثيات خارج النطاق المُعلَن وستُقيَّد،
// لذا اقرأ JSON المحاور بدلاً من افتراض حدود خط
// {"axes":[{"tag":"wght","min":100,"default":400,"max":900}, ...]}

ملاحظات عملية لخط أنابيب خطوط العلامة التجارية

خزّن التجسيدات مؤقتًا حسب مجموعة الإحداثيات. فكل مجموعة إحداثيات مميزة برنامج خط مميز، بحيث يُضمِّن مستند يمزج وزن 400 ووزن 620 مجموعتين فرعيتين بدلاً من وجه مشترك واحد. وتوليد التجسيد نفسه بشكل متكرر عمل مهدور؛ وتوليد عشرات الأوزان شبه المتطابقة حجم ملف مهدور

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

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

استيراد خطوط الويب، والتجسيد المتغير، والتجزئة الفرعية، والرجوع، كلها جزء من خط أنابيب الخطوط نفسه لـ Delphi و C++Builder و Free Pascal؛ وقائمة الميزات الكاملة على صفحة PDF Library لـ Delphi