يختار المصمم خطًا بحرف a من طابق واحد للعناوين، أو صفرًا مشطوبًا للجداول، أو مجموعة من الأحرف الكبيرة المزخرفة للغلاف. هذه الأشكال (glyphs) موجودة في الخط بالفعل. إنها ببساطة ليست الافتراضية. يتم تعيين حرف a الافتراضي من الحرف عبر جدول cmap إلى شكل (glyph) واحد، بينما يقع البديل على بعد بضعة معرفات للأشكال، ولا يمكن الوصول إليه إلا من خلال قاعدة استبدال. إنتاج هذا البديل في ملف PDF يعني قراءة القاعدة وإصدار الشكل البديل في دفق المحتوى. تتناول هذه المقالة قراءة تلك القواعد، من نوع الاستبدال الفردي، في Object Pascal مع عدم وجود مكتبة تشكيل أصلية تحته
النطاق ضيق عن قصد. المجموعات والبدائل الأسلوبية عبارة عن استبدالات إدخال شكل واحد، وإخراج شكل واحد. إنها الجزء من تخطيط OpenType الذي يمكنك حله باستخدام مسح صغير ومحدد للجدول، مما يجعلها مناسبة تمامًا لمحرك Pascal الذي يريد البقاء خاليًا من تبعيات C
لماذا Delphi النقي بدلاً من HarfBuzz
HarfBuzz هو الإجابة الواضحة لـ "تشكيل هذا النص"، وللتشكيل ثنائي الاتجاه بالكامل، أو الهندي، أو العربي، فهو الإجابة الصحيحة. وهي أيضًا مكتبة C. إن ربطها في منتج Delphi أو C++Builder يعني شحن كائن أصلي لكل منصة وبنية مستهدفة، ومطابقة اصطلاح الاستدعاء الخاص بها، وتتبع وتيرة إصدارها، وقراءة شروط ترخيصها مقابل شروطك الخاصة. لا شيء من هذا صعب في حد ذاته. لكنه يمثل احتكاكًا لا يزول أبدًا، ولا يفيد في شيء عندما يكون المطلب الفعلي هو "أعطني صيغة ss01 من هذا الحرف"
لا يحتاج الاستبدال الفردي إلى محرك تشكيل. بل يحتاج إلى محلل لمجموعة من تنسيقات الجداول الفرعية لـ GSUB وبحث ثنائي أو اثنين. كتابة ذلك في Pascal تحافظ على سلسلة الأدوات بأكملها داخل مترجم واحد. الحد الصادق هو أن هذا النهج يتعامل مع عمليات بحث استبدال الأشكال ولا شيء غير ذلك. إنه ليس حلاً ثنائي الاتجاه (bidi)، ولا إعادة ترتيب هندي، ولا تشكيلاً سياقيًا تلقائيًا. في الحالات التي تكون فيها هذه الأشياء ضرورية، فهي ضرورية، ولن يحل استعلام الاستبدال الفردي محلها
التسلسل الهرمي لـ GSUB، من الأعلى إلى الأسفل
يتم تنظيم جدول استبدال الأشكال كسلسلة من التوجيهات غير المباشرة، ويقوم استعلام الاستبدال بالسير في السلسلة من الأعلى. في الأعلى توجد ScriptList. تحدد علامة البرنامج النصي مثل latn إدخالًا، والعلامة الخاصة DFLT هي البرنامج النصي الافتراضي الذي يتم تطبيقه عندما لا يتطابق أي برنامج نصي أكثر تحديدًا. يشير إدخال البرنامج النصي إلى LangSys، نظام اللغة، مع LangSys افتراضي للحالة الشائعة وأخرى مسماة اختيارية للغات التي تحتاج إلى سلوك مختلف. التركية هي المثال المعتاد، حيث تتطلب حرف i المنقطة وغير المنقطة معالجتها الخاصة
يسمي LangSys مجموعة من فهارس الميزات. يشير كل فهرس إلى FeatureList، حيث يحمل سجل الميزة علامة من أربعة بايت، من بينها ss01، وقائمة بفهارس البحث. تشير هذه الفهارس أخيرًا إلى LookupList، حيث توجد جداول الاستبدال الفرعية الفعلية. لذا فإن حل ss01 يعني: العثور على البرنامج النصي، والعثور على LangSys الخاص به، والعثور على الميزة التي علامتها ss01، وجمع عمليات البحث التي يسميها، وتطبيقها. يفتقر HotPDF إلى برنامج DFLT النصي و LangSys الافتراضي، وهو ما تشحنه الغالبية العظمى من تصميمات النصوص اللاتينية، وهو يوفر طريقة لتجاوز علامة البرنامج النصي عندما يربط الخط ميزاته تحت برنامج نصي معين بدلاً من ذلك
تقرر جداول التغطية (Coverage) من يشارك
يبدأ كل جدول فرعي للاستبدال بنفس السؤال: هل يشارك هذا الشكل المدخل في هذه القاعدة، وإذا كان الأمر كذلك، فأين يقع في فهرسة القاعدة نفسها. تتم الإجابة على هذا السؤال من خلال جدول التغطية (Coverage)، والإجابة عبارة عن فهرس تغطية، وهو ترتيب صغير يستخدمه باقي الجدول الفرعي للبحث عما سيصبح عليه الشكل
تأتي التغطية في تنسيقين. التنسيق 1 هو قائمة بمعرفات الأشكال مرتبة ترتيبًا تصاعديًا. تجد شكلًا (glyph) باستخدام بحث ثنائي، وموقعه في القائمة هو فهرس التغطية الخاص به. التنسيق 2 عبارة عن قائمة بسجلات النطاقات، وكل منها عبارة عن شكل بداية، وشكل نهاية، وفهرس التغطية الذي يعين إليه شكل البداية. يحصل الشكل داخل النطاق على فهرس التغطية الخاص به عن طريق الإزاحة من بداية النطاق. يكون التنسيق 1 مضغوطًا عندما تكون الأشكال المشاركة مبعثرة، والتنسيق 2 عندما تقع في تسلسلات متجاورة. كلاهما مرتبان، لذلك يتم البحث فيهما في وقت لوغاريتمي، وكلاهما يُرجع إما فهرس تغطية أو رسالة نظيفة "غير مغطاة" (not covered) تتيح للمحرك ترك الشكل كما هو
الاستبدال الفردي (Single Substitution)، التنسيقان
الاستبدال الفردي هو LookupType 1، وهو يعين شكلًا واحدًا إلى بديل واحد بالضبط. وله أيضًا تنسيقان، والتقسيم هو تحسين للمساحة. يخزن التنسيق 1 قيمة دلتا موقعة واحدة. معرف الشكل الناتج هو معرف الشكل المدخل بالإضافة إلى تلك الدلتا، مع المقياس 65536 (modulo). هذه هي الطريقة التي يشفر بها الخط استبدالًا حيث يجلس كل شكل مشارك على نفس الإزاحة الثابتة من بديله، على سبيل المثال كتلة من الأرقام المحاذاة توضع على مسافة ثابتة من الأرقام ذات النمط القديم المطابقة. يحدد جدول التغطية الأشكال المؤهلة، وتخدم الدلتا الواحدة جميعها
يخزن التنسيق 2 مصفوفة صريحة لمعرفات الأشكال البديلة. فهرس التغطية من جدول التغطية هو الفهرس في تلك المصفوفة، لذا فإن الشكل الموجود في فهرس التغطية 0 يصبح الإدخال الأول في المصفوفة، وفهرس التغطية 1 الثاني، وهكذا. يُستخدم التنسيق 2 عندما لا تكون البدائل في إزاحة موحدة، وهي الحالة الشائعة للمجموعات الأسلوبية المبنية يدويًا. الاستعلام هو نفسه من جانب المستدعي في كلتا الحالتين. خذ الشكل المدخل، وقم بتمريره عبر التغطية، وإذا تمت تغطيته، فقم بتطبيق الدلتا أو قراءة فتحة المصفوفة
var
Pdf: THotPDF;
BaseGID, AltGID: Word;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.BeginDoc;
Pdf.RegisterUnicodeTTF('C:\Fonts\MyStylisticFace.ttf');
Pdf.SetFont('My Stylistic Face', 12, []);
// Default glyph for 'a' through the font's cmap.
BaseGID := Pdf.GetUnicodeGlyphForCodepoint(Ord('a'));
// Stylistic Set 1: resolve the alternate via GSUB LookupType 1.
AltGID := Pdf.GetSingleSubstituteGlyph(BaseGID, 'ss01');
// AltGID = BaseGID means the feature did not touch this glyph.
if AltGID <> BaseGID then
{ emit AltGID in the content stream };
finally
Pdf.Free;
end;
end;
العقد الجدير بالملاحظة هو التمرير. يقوم GetSingleSubstituteGlyph بإرجاع معرف الشكل المدخل دون تغيير عند كل إخفاق: لا يوجد خط، لا يوجد جدول GSUB، لا توجد ميزة متطابقة، لا توجد تغطية. هذا يعني أن الاستدعاء آمن لإجرائه دون قيد أو شرط. أنت تطلب البديل، وإذا لم يكن هناك بديل، فستستعيد بالضبط ما وضعته، لذلك لا يحتاج الكود المستدعي أبدًا إلى تخصيص حالة لخط يفتقر إلى الميزة
ماذا تعني علامات الميزة الأسلوبية
علامة الميزة هي المفردات الكاملة التي تحدد البديل الذي تطلبه، والعلامات المتعلقة بالعمل الأسلوبي هي قائمة قصيرة. الزوج الرئيسي هو salt، البدائل الأسلوبية، وهو وصول شامل إلى الأشكال البديلة للشكل، و ss01 حتى ss20، العشرين مجموعة أسلوبية مرقمة التي يمكن أن يحددها الخط، كل منها عبارة عن حزمة مسماة من الاستبدالات التي يجمعها المصمم معًا. قد يضع الخط حرف a من طابق واحد و R بساق مستقيمة تحت ss03، على سبيل المثال، لذا فإن تمكين هذه المجموعة الواحدة يعيد تصميم كليهما
حول هؤلاء توجد العديد من علامات الاستبدال الفردي الأخرى. aalt هو الوصول إلى جميع البدائل (access-all-alternates)، وهو اتحاد لكل بديل يمتلكه الشكل، وعادة ما يتم تقديمه كميزة لوحة أشكال. تحدد titl الأحرف الكبيرة للعناوين المقطوعة للأحجام الكبيرة. يقوم subs و sups بالتبديل في أرقام الأحرف المنخفضة والمرتفعة الحقيقية بدلاً من الإعدادات الافتراضية المصغرة. ينتج ordn الأشكال الترتيبية، الحروف المرتفعة في 1st و 2nd. يبني frac الكسور، على الرغم من أن الكسور القطرية الكاملة تعتمد أيضًا على أربطة ومنطق سياقي يتجاوز الاستبدال الفردي العادي. بالنسبة لحالات الشكل الواحد، فإن الآلية متطابقة مع ss01: قم بتمرير العلامة إلى استعلام الاستبدال واقرأ الشكل البديل
// Try a stylistic-set feature, then fall back to plain alternates.
function ResolveAlternate(Pdf: THotPDF; BaseGID: Word;
const PreferredTag: AnsiString): Word;
begin
Result := Pdf.GetSingleSubstituteGlyph(BaseGID, PreferredTag);
if Result = BaseGID then
Result := Pdf.GetSingleSubstituteGlyph(BaseGID, 'salt');
// Still BaseGID if neither feature covers this glyph.
end;
تنسيق cmap 12 والمستويات الإضافية
قبل أن يتم تشغيل أي استبدال، يجب أن يصبح الحرف (character) شكلًا (glyph)، وهذه هي مهمة جدول cmap. يبدأ استعلام الاستبدال من معرف الشكل، لذلك يكون المسار دائمًا من الحرف إلى الشكل من خلال cmap، ثم من الشكل إلى البديل من خلال GSUB. الجزء المثير للاهتمام من cmap هو نطاقه. يغطي الجدول الفرعي بالتنسيق 4 المستوى الأساسي متعدد اللغات (BMP)، وهو أول 65536 نقطة رمز، وهذا يكفي لمعظم النصوص اللاتينية. وهو لا يكفي لنقاط الرمز من U+10000 فصاعدًا، وهي المستويات الإضافية، حيث تعيش الآن الأرقام والحروف الرياضية والعديد من الرموز والعديد من البرامج النصية الحية
التنسيق 12 هو الجدول الفرعي الذي يغطي النطاق الكامل من U+0000 إلى U+10FFFF. إنها قائمة مرتبة من المجموعات، وكل مجموعة عبارة عن نقطة رمز البداية ونقطة رمز النهاية ومعرف شكل البداية، لذا يتم تعيين تسلسل متجاور من نقاط الرمز إلى تسلسل متجاور من الأشكال. يقوم HotPDF بحل نقاط الرمز من خلال استراتيجية هجينة تتطابق مع كيفية تشكيل البيانات. يتم تقديم نقاط الرمز في BMP من مصفوفة مباشرة مفهرسة بنقطة الرمز، وهي عملية بحث واحدة بدون بحث. يتم تقديم نقاط الرمز في المستويات الإضافية من جدول متناثر مرتب بواسطة نقطة الرمز ويتم البحث فيه باستخدام بحث ثنائي. والنتيجة هي أن GetUnicodeGlyphForCodepoint يأخذ Cardinal كاملًا ويجيب بشكل صحيح عبر النطاق بأكمله، ويعيد معرف الشكل 0، الشكل .notdef، لأي نقطة رمز لا يعينها الخط
var
Pdf: THotPDF;
Cp: Cardinal;
GID, StyledGID: Word;
begin
// A supplementary-plane code point: U+1D49C MATHEMATICAL SCRIPT CAPITAL A.
Cp := $1D49C;
GID := Pdf.GetUnicodeGlyphForCodepoint(Cp); // format 12 lookup
if GID <> 0 then
StyledGID := Pdf.GetSingleSubstituteGlyph(GID, 'ss01')
else
StyledGID := 0; // font has no glyph for this code point
end;
أين تتوقف هذه الاستعلامات
تجيب واجهات برمجة تطبيقات الاستبدال الفردي على شكل واحد من الأسئلة، ومن الجدير توضيح ما لا تجيب عليه. LookupType 1 هو واحد من ثمانية أنواع استبدال. لا يتعامل الاستعلام مع الاستبدال المتعدد LookupType 2، حيث يصبح شكل واحد عدة أشكال، ولا الاستبدال المرتبط LookupType 4، حيث تصبح عدة أشكال شكلًا واحدًا. إنه لا يتعامل مع الأنواع السياقية والسياقية المتسلسلة، LookupTypes 5 و 6، التي تنطلق فقط عندما يظهر شكل في جوار معين، ولا أنواع الامتداد والسلسلة العكسية. إن الكسر القطري، أو الربط الديفاناغاري، أو التسلسل العربي الأولي-الوسطي-النهائي هو مشكلة تسلسل، ولا يمكن للبحث الفردي للاستبدال لكل شكل أن يعبر عنه
كما أنه لا يقوم بالتشكيل التلقائي. لا يوجد شيء هنا يفحص سلسلة من النصوص، ويقرر أي الميزات يجب تشغيلها، ويطبقها بالترتيب الذي يتطلبه البرنامج النصي. يختار المستدعي علامة الميزة ويطبقها شكلًا بشكل. هذه هي الأداة المناسبة تمامًا للمجموعات والبدائل الأسلوبية، والتي تعد اختيارية ومحلية، وهي أداة خاطئة تمامًا للبرنامج النصي الذي يحتاج إلى إعادة الترتيب. إن إبقاء الحدود واضحة هو ما يتيح لمسار الاستبدال البقاء صغيرًا ويمكن التنبؤ به
بالنسبة للحالات التي تحتاج إلى عمل على مستوى التسلسل، تم تناول قصة البرامج النصية المعقدة في مقالتنا حول تشكيل نص البرامج النصية المعقدة في Delphi. إذا كانت الاستبدالات الخاصة بك جزءًا من وظيفة إعداد تقارير أكبر تضع أيضًا صورًا وخطوطًا أخرى على الصفحة، فإن الدليل لإخراج التقرير بالخطوط والصور يغطي كيفية تناسب هذه القطع معًا. كل هذا يعمل على نفس المحرك، مكون HotPDF لـ Delphi و C++Builder، والذي يحمل استعلامات استبدال GSUB جنبًا إلى جنب مع تضمين الخطوط وتقسيمها وواجهات برمجة تطبيقات النص التي تمت تغطيتها في مكان آخر في هذه المدونة