يقلّص HotXLS، مكوّن Excel لـDelphi وC++Builder، حجم خط PDF المضمَّن عبر تجزيء خطوط TrueType: عند تصدير PDF، يستدعي الدالة CreateFontPackage من مكتبة نظام ويندوز fontsub.dll لإعادة بناء خط TrueType مضمَّن حول نقاط الشيفرة اليونيكودية التي استخدمتها ورقة العمل فعليًا فقط، بدلًا من شحن ملف الخط بأكمله. تقرير من مائتي صف من أسماء منتجات صينية قد لا يحتاج إلا لبضع مئات من محارف هان (Han) المميزة، ومع ذلك فإن خطوط CJK التي يشحنها ويندوز تتراوح روتينيًا بين 5 و20 ميجابايت للواحد. ضمّن خطًا كاملًا، ويمكن للخط وحده أن يفوق وزن كل كائن آخر في PDF مجتمعًا
fontsub.dll ليست مكتبة سمع بها معظم مطوري Delphi يومًا، ولذلك سبب: يشحنها Microsoft كمكتبة DLL أداتية صغيرة قليلة التوثيق بدلًا من واجهة Win32 API بارزة. يعامل HotXLS إياها كقدرة اختيارية، لا اعتمادًا صارمًا، لذا فإن كيفية تحميل المُصدِّر لها، واستدعائها، والتراجع عند غيابها، تخبرنا عن البرمجة الدفاعية في ويندوز بقدر ما تخبرنا عن صيغ الخطوط، وكلا نصفي تلك القصة يستحقان الاستعراض
لماذا يتضخم تصدير PDF في HotXLS مع النص اليونيكودي؟
يلجأ مُصدِّر PDF في HotXLS إلى خط TrueType مضمَّن فقط عندما يقع نص ورقة العمل خارج WinAnsi، ويبقى على عائلة Helvetica المدمجة في بقية الأوقات، وهو المسار الافتراضي الذي يشرحه شرح تصدير ورقة العمل إلى PDF بتفصيل. تغطي WinAnsi النص الأوروبي الغربي بشكل جيد بما يكفي لدرجة أن الكثير من مصنّفات العمل لا تُشغّل تضمين خط أبدًا: يشير PDF فقط إلى Helvetica بالاسم ويوفّرها القارئ محليًا، بحيث يبقى الملف صغيرًا. بمجرد أن تحمل خلية شيئًا لا تستطيع WinAnsi تمثيله، اسم منتج صيني، ملاحظة كورية، رمز شارد في تعليق، يتعيّن على المُصدِّر تضمين برنامج خط فعلي، لأن قارئ PDF لا يملك مصدر حرف احتياطي للمحارف خارج الخطوط الأربعة عشر القياسية
يحدد HotXLS ذلك الخط تلقائيًا، بمسح مجلد خطوط ويندوز بحثًا عن قائمة قصيرة من المرشحين المثبَّتين، بما في ذلك خطوط CJK القادرة التي يشحنها ويندوز لرسم الصينية والكورية، ما لم تكن خاصية UnicodeFontFile الخاصة بالمُصدِّر تشير بالفعل إلى ملف محدد، وأيًّا كان الخط الذي يستقر عليه، يُضمَّن بالكامل قبل تشغيل التجزيء إطلاقًا. متطلب التضمين ذلك خاص بـPDF: تحافظ مسارات تصدير RTF وHTML في HotXLS على سلامة النص اليونيكودي بترميز نقاط الشيفرة هربًا داخل تدفق البايتات بدلًا من شحن برنامج خط، وهذا سبب عدم وجود ما يعادل مشكلة الحجم التي تغطيها هذه المقالة في هاتين الصيغتين
uses
lxHandle, lxPDF;
var
Book: TXLSWorkbook;
Exporter: TXLSPDFExport;
begin
Book := TXLSWorkbook.Create;
try
Book.Open('catalog-cn.xlsx');
Exporter := TXLSPDFExport.Create;
try
// Optional: pin a specific CJK-capable font instead of the
// exporter's automatic Windows\Fonts scan.
Exporter.UnicodeFontFile := 'C:\Windows\Fonts\simhei.ttf';
Exporter.SaveAsPDF(Book.ActiveSheet, 'catalog-cn.pdf');
finally
Exporter.Free;
end;
finally
Book.Free;
end;
end;
ما هي fontsub.dll، ولماذا لا تُكتب أداة تجزيء من الصفر؟
fontsub.dll مكتبة نظام ويندوز صغيرة، تُشحن منذ Windows XP، تعرض دالة واحدة ذات صلة هنا: CreateFontPackage. سلّمها بايتات خط TrueType مصدري وقائمة نقاط شيفرة يونيكودية يجب الاحتفاظ بها، وستُعيد خطًا أدنى لا يزال يحقق كل قيد في صيغة الخط: فهارس حروف مُعاد ترقيمها، وجداول glyf وloca مُعاد بناؤها حول المخططات المُبقاة فقط، وhmtx وcmap مُعاد كتابتهما لتطابق ذلك. يُعلن HotXLS نوع مؤشر الدالة مباشرة مقابل ذلك العقد
const
TTFCFP_FLAGS_SUBSET = 1;
TTFMFP_SUBSET = 0;
TTFCFP_MS_PLATFORMID = 3;
TTFCFP_UNICODE_CHAR_SET = 1;
type
TCreateFontPackage = function(puchSrcBuffer: Pointer; ulSrcBufferSize: Cardinal;
var puchFontPackageBuffer: PAnsiChar; var pulFontPackageBufferSize: Cardinal;
var pulBytesWritten: Cardinal; usFlags, usTTCIndex, usSubsetFormat,
usSubsetLanguage, usSubsetPlatform, usSubsetEncoding: Word;
pusSubsetKeepList: PWordArray; usSubsetKeepListCount: Word;
lpfnAllocate, lpfnReAllocate, lpfnFree, reserved: Pointer): Cardinal; cdecl;
كتابة مهمة CreateFontPackage يدويًا بدلًا من استدعائها كانت ستعني تنفيذ أداة تجزيء TrueType صحيحة: اجتياز الحروف المركبة لجلب كل حرف مكوّن يشير إليه حرف مُبقًى، وإعادة بناء إزاحات loca بعد إسقاط المخططات، واحترام بتات إذن التضمين في جدول OS/2 الخاص بالخط، وإتقان كل ذلك عبر أيًّا كانت الخطوط الغريبة التي صادف أن جهاز عميل مثبَّتة عليه. حلّت Microsoft تلك المشكلة بالفعل وتشحن الحل كجزء من ويندوز نفسه، لذا فإن استدعاء مكتبة DLL نظامية تصونها هي، وتختبرها مقابل مكدس رسم الخطوط الخاص بها، وتوزّعها على كل جهاز مجانًا، يكلّف HotXLS تحميلًا ديناميكيًا ومؤشر دالة؛ بينما إعادة تنفيذ المنطق نفسه كانت ستعني امتلاك مُحلِّل لصيغة ثنائية بها عقود من الحالات الحدّية، لأجل ميزة لا تهم إلا عندما يصادف أن يكون خط ما كبيرًا
بناء قائمة الاحتفاظ من الحروف المرسومة فعليًا
يبني HotXLS قائمة احتفاظ التجزيء من خريطة كان يحتفظ بها بالفعل لسبب مختلف، بحيث لا تكلف المحاسبة شيئًا إضافيًا. في كل مرة ترسم فيها شيفرة رسم الصفحة حرفًا يحتاج خط اليونيكود المضمَّن، تبحث عن فهرس حرف ذلك الرمز وتسجّل الاقتران في FUnicodeGlyphMap، وهو جدول من الحرف إلى نقطة الشيفرة يقود أيضًا خريطة ToUnicode CMap الخاصة بـPDF بحيث يعيد النسخ واللصق من المستند النهائي النص الأصلي بدلًا من معرّفات حروف خام. بحلول انتهاء تدفقات محتوى الصفحة، تسرد تلك الخريطة بالفعل مجموعة نقاط الشيفرة اليونيكودية التي استخدمها المستند بالضبط، لا أكثر ولا أقل
var
keepList: array of Word;
keepCount, i: Integer;
codePoint: LongWord;
begin
SetLength(keepList, FUnicodeGlyphMap.Count);
keepCount := 0;
for i := 0 to FUnicodeGlyphMap.Count - 1 do
begin
codePoint := LongWord(StrToIntDef('$' + FUnicodeGlyphMap.ValueFromIndex[i], 0));
if codePoint > 0 then
begin
keepList[keepCount] := Word(codePoint);
Inc(keepCount);
end;
end;
end;
عند وقت الإنهاء، يجتاز HotXLS تلك الخريطة نفسها مرة ثانية لبناء قائمة الاحتفاظ التي تتوقعها CreateFontPackage، وهي مصفوفة بسيطة من نقاط الشيفرة اليونيكودية المطلوب الاحتفاظ بها بصيغة 16 بت التي تتطلبها وسيطة قائمة الاحتفاظ في الواجهة البرمجية. ولأن تلك الوسيطة مصفوفة من كلمات 16 بت، فهي تعالج المستوى الأساسي متعدد اللغات (BMP) بنظافة، وهو ما يغطي نص CJK العادي والسيريلي واليوناني والعربي دون تعقيد؛ ورقة عمل تعتمد على محارف المستوى التكميلي، بعض الرموز التعبيرية أو نصوص تاريخية نادرة، تقع خارج ما يمكن لإدخال قائمة احتفاظ واحد تسميته مباشرة، وهذا حد يستحق المعرفة لا عيبًا، بما أن الغالبية العظمى من جداول البيانات التجارية الكثيفة اليونيكود لا تقترب من ذلك المستوى أصلًا
ماذا يحدث عندما تكون fontsub.dll مفقودة؟
لا يفترض HotXLS أبدًا وجود fontsub.dll، ولا يفشل تصدير PDF أبدًا بسبب غيابها. تُحمَّل المكتبة ديناميكيًا في لحظة الحاجة إلى تجزيء، عبر SafeLoadLibrary وGetProcAddress بدلًا من استيراد ثابت، تحديدًا لأن fontsub.dll ليست واجهة برمجية عامة موثّقة ومضمونة الوجود بالطريقة التي عليها kernel32.dll: إنها أداة تضمين خطوط مُجمَّعة، ولا شيء في عقد Microsoft يعد ببقائها في كل إصدار، وكل فرع صيانة، وكل طبقة توافق تحاول محاكاة ويندوز
var
hFontSub: HMODULE;
CreateFontPackage: TCreateFontPackage;
begin
hFontSub := SafeLoadLibrary('FontSub.dll');
if hFontSub = 0 then
Exit; // no subsetting available - keep the full embedded font
try
@CreateFontPackage := GetProcAddress(hFontSub, 'CreateFontPackage');
if not Assigned(CreateFontPackage) then
Exit;
// ... call CreateFontPackage, check its return code ...
finally
FreeLibrary(hFontSub);
end;
end;
كل مسار فشل يطوي مرة أخرى إلى النتيجة نفسها. مكتبة DLL مفقودة، تصدير مفقود، رمز إرجاع غير صفري، أو خط يمنع جدول OS/2 الخاص به التجزيء عبر بتات إذن التضمين، يحتفظ HotXLS ببساطة بالخط الكامل الذي كان قد ضمّنه بالفعل ويمضي قدمًا. لا شيء يُطلق استثناءً، ولا شيء يُجهض التصدير، ولا تحتاج الشيفرة المستدعية أبدًا لتغليف تحسين خط بمعالجة استثناءات خاصة بها؛ ملف PDF المُصدَّر صالح في كلتا الحالتين، والمتغير الوحيد هو ما إذا كان سينتهي صغيرًا أو أكبر إلى حد ما
ما مقدار الانكماش الفعلي لملف PDF؟
يقلّص تجزيء خطوط TrueType في HotXLS عادة ملف PDF المُصدَّر من ورقة عمل كثيفة اليونيكود إلى مكان ما بين جزء من عشرين وجزء من ثمانية من حجمه غير المُجزَّأ، تخفيض بمقدار 8 إلى 20 ضعفًا يتتبع مقياسه مقدار ما يلمسه مستند معيّن فعليًا من خط كامل: طلب شراء مبني حول بضع مئات من المحارف الصينية المميزة يحتفظ فقط بتلك المئات القليلة من الحروف من بين عشرات الآلاف التي يشحنها خط CJK، بينما تحتفظ ورقة تمتد عبر مزيج أوسع من المحارف بحصة أكبر تناسبيًا. يضيف HotXLS تمريرة ضغط Flate أخرى فوق بايتات الخط المُجزَّأ قبل كتابتها في تدفق /FontFile2 الخاص بـPDF، الضغط نفسه الذي تمر به بالفعل بقية تدفقات محتوى المستند، ولا شيء من هذا يطلب أي شيء إضافي من الشيفرة المستدعية: ورقة عمل لا تغادر WinAnsi أبدًا لا تلمس هذا المسار أبدًا وتستمر في التصدير عبر Helvetica العادي، بينما ورقة عمل تُشغّل مسار خط اليونيكود تحصل على التجزيء تلقائيًا، دون خاصية يجب ضبطها ودون استدعاء منفصل يجب إجراؤه، والخاصية الوحيدة المعنية، UnicodeFontFile، تختار فقط أي خط يُضمَّن ويُجزَّأ، لا ما إذا كان التجزيء يحدث
تجزيء الخطوط تفصيل واحد داخل سطح تصدير PDF الأوسع في مكوّن HotXLS لـExcel في Delphi، إلى جانب التقسيم إلى صفحات، وبيانات طباعة ورقة العمل الوصفية، ومسارات تصدير CSV وHTML وRTF التي يشحن بها