حين لا يضمّن PDF خطاً ما، يعرض مكوّن HotPDF ذلك النص بخط ويندوز مثبّت يختاره HPDFMapBaseFontToSystem: تفك اسم /BaseFont، وتقلع جزء النمط، وتجرب عدة تهجئات حتى يؤكد GDI أن العائلة مثبتة، وتقيس عروض الخطوط الـ 14 القياسية المفقودة من خطوط متوافقة المقاييس، وتحول الأكواد أحادية البايت إلى Unicode قبل الرسم. كل خطوة من تلك موجودة لأن النسخة الساذجة فشلت على ملفات واقعية. و عارض الصفحات RenderLoadedPageToBitmap يعالج البرامج المضمّنة جيداً؛ وهذه قصة الخطوط التي ليست في الملف أصلاً
لماذا يرسم GDI بصمت محرفاً خاطئاً لخط غير مضمن؟
لا يبلّغ GDI عن خط مفقود أبداً: سلّم CreateFontIndirect اسماً لوجه لا يعرفها فيختار بديلاً بهدوء، غالباً serif آخر بلا وزن ثقيل. كان العارض الأقدم يمرر اسم PDF شبه حرفي، فـ TimesNewRoman,Bold و TimesNewRomanPS-BoldMT و SegoeUI-Semibold لم تطابق شيئاً وخرجت بما اختاره GDI. والأسماء يمكن أن تكون أسوأ. تسمح ISO 32000-1 §7.3.5 لاسم أن يكتب أي بايت على صورة #xx، وينتجو CJK عادة تهجئة أسماء الخطوط بـ UTF-8 مهرّبة أو بايتات صفحة أكواد قديمة؛ وقبل v2.766.69 صارت الهرّبات نفسها اسم الوجه. تفك HPDFMapBaseFontToSystem الآن الهرّبات أولاً، وتعيد تسلسل بايتات UTF-8 صالحاً بوصفه محارفها، وتقرأ أي بايتات علوية أخرى في صفحة أكواد النظام
وقطع النمط هو حيث تعض الاستدلالات. الفاصلة تنهي العائلة دائماً (Arial,Bold تعطي Arial)، لكن الشرطة تفعل ذلك فقط حين تكون الكلمة بعدها نمطاً: Bold و Italic و Oblique و Regular و Roman و Medium و Light و Black و Heavy و Semi و Demi و Thin و Extra و Ultra و Condensed. تحفظ تلك القاعدة MS-Mincho كاملة بينما تحول Calibri-Light إلى Calibri. ثم تجرب HotPDF تهجئات العائلة، تليها تهجئات العائلة بعد قلع لاحقة PSMT أو MT أو PS. ومنذ v2.768.18 تغطي تلك التهجئات كل اختيار للفراغات في المواضع التي يمكن أن تبدأ عندها كلمة: قبل حرف كبير يلي حرفاً صغيراً (MyriadPro تصير Myriad Pro)، وعند آخر حرف كبير في جري يليه حرف صغير (UIGothic)، وبعد MS افتتاحية (MSPGothic)، من الصورة المتباعدة كلياً نزولاً إلى الاسم كما كُتب؛ وبعد أربعة مواضع كهذه لا تُجرب سوى الصورة المتباعدة كلياً والاسم الملتحق. لا يمكن تطبيق التباعد بعمى، لأن ويندوز يبقي بعض الكلمات ملتحقة: SimSun مثبتة بتلك التهجئة بالضبط، بينما MicrosoftYaHei و MicrosoftJhengHei و MSPGothic تعود إلى Microsoft YaHei و Microsoft JhengHei و MS PGothic. وقبل v2.768.18 كان المُعيّن يضع فراغاً قبل كل حرف كبير داخلي، فبحث عن MicrosoftYaHei بوصفها Microsoft Ya Hei ولم يجدها أبداً. يُحسب المرشح مثبتاً حين تعيد CreateFontIndirect يليها GetTextFace الاسم المطلوب، أو، منذ v2.768.18، حين يسرد جدول name للخط المختار اسماً بوصفه عائلة كاملة أو عائلة طباعية بأي لغة؛ والجواب يُخزن لكل اسم، فمستندات كثيرة الخطوط غير المثبتة لم تعد تفحص ويندوز عن كل اسم في كل صفحة
ولأن دالة التعيين عامة في وحدة HPDFRenderFontMetrics، يستطيع تقرير preflight أن يعرض أي عائلة مثبتة سيعرض بها كل خط غير مضمن، مستخدماً تعداد الخطوط الذي يكشفه THotPDF أصلاً للمستندات المحمّلة:
uses
HPDFDoc, HPDFRenderFontMetrics;
procedure ListSystemFontMappings(Pdf: THotPDF; Log: TStrings);
var
Page, I: Integer;
Info: THPDFLoadedFontInfo;
begin
for Page := 0 to Pdf.LoadedPageCount - 1 do
for I := 0 to Pdf.GetLoadedFontCount(Page) - 1 do
if Pdf.GetLoadedFontInfo(Page, I, Info) and not Info.IsEmbedded then
Log.Add(Format('page %d /%s %s -> %s',
[Page + 1, string(Info.ResourceName), string(Info.FontName),
HPDFMapBaseFontToSystem(Info.FontName)]));
end;
كيف تقيس HotPDF خطوط 14 القياسية التي بلا /Widths؟
تقيس HotPDF التقدمات المفقودة على الخط المثبت ذي المقاييس نفسها، لأن ISO 32000-1 §9.6.2.2 تسمح لخطوط الـ 14 القياسية بحذف /Widths والمكتبة لا تشحن جداول AFM. تحمل Arial مقاييس Helvetica، و Times New Roman تحمل Times، و Courier New تحمل Courier، فتنشئ HPDFMeasureBaseFontWidths الوجه الموافق عند lfHeight = -1000 وتستدعي GetCharWidth32W؛ وعند ذلك الارتفاع تكون النتيجة أصلاً بوحدات 1/1000 em التي تستخدمها عروض PDF. ويرسل العارض أولاً كل كود إلى Unicode عبر /Encoding و /BaseEncoding و /Differences، بالافتراض StandardEncoding. وتصدير SVG واستخراج النص يصطدمان فخاً آخر: خط Type 1 قياسي بلا /Encoding إطلاقاً أنتج مفكك ترميز بلا معلومات ترميز، ولم يسجّله تصدير SVG قط، وذهبت كل عرض مقاس هدراً. وتوريد StandardEncoding الضمنية أصلحه، بشرط وسمها بوصفها ترميزاً معرفاً سلفاً؛ وتمريرها في مسار اسم CMap يفك كل كود إلى 0 وتتبعه كل العروض
ثخانة وميلان وإزاحة بواحد في أعلام واصف الخط
يرقّم مدخل /Flags لواصف الخط بتاته من 1 لا من 0، فـ ForceBold هي البت 19 ($40000) و Italic هي البت 7 ($40)، وفق ISO 32000-1 الجدول 123. وكان الكود القديم يفحص $20000، أي البت 18، SmallCap. نجا الخطأ من v2.345.0 إلى v2.766.53 لأن /FontDescriptor مرجع غير مباشر في كل الأحوال تقريباً وبانئ الخطوط يقرأ الكائنات المباشرة فقط، فلم يعمل فرع الأعلام كله قط، ونفس العمى تجاهل /Widths 12 0 R وصب النص بتقدم احتياطي 500 وحدة. وبمجرد أن بدأت v2.766.53 تحل المراجع غير المباشرة عبر العارض وجب تصحيح البت في التغيير نفسه، وإلا لعرض كل وجه SmallCaps فجأة ثخيناً:
const
// ISO 32000-1 جدول 123 تعد مواضع البت من 1
FD_ITALIC = $00040; // بت 7
FD_SMALLCAP = $20000; // بت 18، ليس ثخاناً
FD_FORCEBOLD = $40000; // بت 19
procedure ApplyDescriptorFlags(Flags: Integer; var LF: TLogFont);
begin
if (Flags and FD_FORCEBOLD) <> 0 then
LF.lfWeight := FW_BOLD;
if (Flags and FD_ITALIC) <> 0 then
LF.lfItalic := 1;
end;
لماذا تأخذ خطوط CJK ذات CMap من نوع UCS2 العروض الخاطئة؟
يرسم نص CJK بـ CMap من نوع UCS2 معرف سلفاً الـ glyphs الصحيحة والعروض الخاطئة حين يعامل العارض الكود بوصفه CID، لأن /W مفهرسة بالـ CID لا بكود المحرف. مع STSong-Light و UniGB-UCS2-H يصادف أن الكود يساوي قيمة Unicode، فيرسم GDI المحارف الصحيحة ويختبئ العيب في التقدمات: الحروف الصغيرة تصل أكواداً 97 فما فوق، فتسقط خارج مدخل /W مثل [1 95 500]، وتأخذ جميعاً العرض الافتراضي /DW البالغ 1000. ومنذ v2.766.56 يقرأ عارض HotPDF الأكواد عبر نطاقات codespace للـ CMap (ISO 32000-1 §9.7.6.2) ويعينها إلى CIDs قبل البحث عن العروض. تستخدم الجداول المدمجة UCS2 و UTF16 وتدفقات CMap المضمّنة وحدها؛ فالتقريب الهووي لشيء مثل GBK-EUC-H كان سيبدو مدعوماً فقط بينما ينتج مخرجاً خاطئاً، فلا يتظاهر العارض
لماذا تتحول المحارف المنوطة إلى علامات استفهام على ويندوز الصيني؟
لا يجوز أبداً أن تبلغ الأكواد أحادية البايت دوال GDI من نوع ANSI («A»)، لأن GetGlyphOutlineA و GetGlyphIndicesA تفسران البايتات في صفحة أكواد النظام بينما تستخدم TextOutA مجموعة محارف الخط المختار. وعلى نظام صيني صار بايت Arial $A9 (علامة الحقوق في Windows-1252) بايتاً قائداً لـ GBK وعرض بعلامة «?»، فخ سلكه مسار المخطط بلا إرشاد المضاف في v2.766.83 مباشرة. تسأل v2.767.3 الخط المحقق عن مجموعة محارفه عبر GetTextCharset، وتحولها إلى صفحة أكواد عبر TranslateCharsetInfo، وتمرر البايت عبر MultiByteToWideChar وتستدعي الدوال W؛ وخطوط الرموز تستخدم U+F000 زائد الكود بدلاً من ذلك. والترميزات المخالفة لـ Windows-1252 — /Differences و StandardEncoding و MacRomanEncoding — تُعين إلى Unicode قبل أن يراها أي خط نظام
ما حدود الرسم بخطوط النظام؟
عرض الخطوط النظامية تقريب، ومكوّن HotPDF صادق حول أين يتوقف. قبل v2.768.18 قارن فحص التثبيت اسماً تعيده GetTextFace وحده، وعلى ويندوز محلّل اللغة تبلّغ تلك الدالة اسم العائلة بلغة النظام، فعُدّت Microsoft YaHei على ويندوز الصيني أو Yu Mincho على الياباني مفقودة ورُسمت ببديل GDI؛ ومنذ v2.768.18 يُبحث عن وجه يعود باسم آخر في جدول name للخط أيضاً، فتوجد تلك الخطوط. والتوافق المقيس مضمون لعائلات Helvetica و Times و Courier وحدها؛ و Symbol تُعين إلى Symbol و ZapfDingbats إلى Wingdings، وهو إسعاف لا مطابقة. ويرى preflight أعلاه أيضاً الخطوط في قاموس /Resources لكل صفحة وحدها، لا تلك المشار إليها من داخل Form XObjects. وحين يظل كود بلا رسم يبلّغ عنه تتبع glyphs غير المحلولة وقت الرسم، وهي إشارة أفضل من تحديق الصور المصغرة
والإصلاح الدائم يقع على جانب التأليف. تكتب HotPDF نفسها بـ FontEmbedding مضبوطة True افتراضياً، مستبدلة Arial مضمّنة حتى حين يستدعي الكود SetFont بـ Helvetica، والنص المضمّن يمر عبر عارض glyphs الخطوط المضمّنة بدل أي تخمين مما سبق. وحرس رخيص للملفات الداخلة هو التحذير قبل العرض حين تكون عائلة معينة غير موجودة في قائمة خطوط الشاشة:
// VCL: تسرد Screen.Fonts أسماء العائلات المثبتة (وحدة Forms)
function MissingSystemFamily(const BaseFont: AnsiString): Boolean;
begin
Result := Screen.Fonts.IndexOf(HPDFMapBaseFontToSystem(BaseFont)) < 0;
end;
وللمكوّن الكامل، بما فيه عرض الصفحات واستخراج النص وتجزئة الخطوط على جانب الكتابة، انظر صفحة منتج HotPDF Delphi PDF component