يستورد HotPDF، مكوّن PDF الأصلي لـDelphi وC++Builder، ملفات EMF وWMF الخاصة بويندوز عن طريق تفسير كل سجل GDI مباشرة إلى عوامل تشغيل PDF بدلًا من تسطيح الملف إلى صورة نقطية: تتحول تعبئات التدرج اللوني إلى أنماط تظليل محوري في PDF، وتتحول فرش التظليل المتقاطع إلى أنماط تبليط في PDF، وتمنع بوابة مركزية لحالة المسار السجلات المشوّهة من إفساد المخرجات. أي مخطط بياني يمكن لـTChart، أو سطح GDI+، أو حتى TCanvas عادي تصديره كملف تعريف معزز (enhanced metafile) هو مرشح لهذا المسار، ويظهر الفرق فور تكبير الصفحة أو إرسالها إلى طابعة عالية الدقة
البديل الذي يلجأ إليه معظم مطوري Delphi افتراضيًا هو ترقيم (rasterize) ملف التعريف إلى صورة نقطية قبل وضعه على الصفحة، والتكلفة لا تظهر إلا لاحقًا: مخطط أعمدة كان واضحًا على الشاشة يتحول إلى مربعات مرئية بمجرد طباعة ملف PDF بدقة 600 نقطة في البوصة أو عرضه على شاشة قاعة اجتماعات، وتنهار منطقة CAD المملوءة بتظليل متقاطع إلى مستطيل رمادي مسطح واحد إذا لم يُحمل نمط التعبئة عبر العملية. قراءة ملف التعريف كبرنامج بدلًا من صورة هي ما يتجنب المشكلتين، وهو المسار الأصعب في التنفيذ الصحيح، ولهذا تستحق المزالق أدناه المعرفة قبل إصدار أي تقرير
لماذا نفسّر ملف التعريف بدلًا من تسطيحه إلى صورة نقطية؟
يبقي HotPDF استيراد EMF وWMF على المسار المتجهي لأن ملف تعريف ويندوز هو تسلسل مسجَّل من استدعاءات رسم GDI، وليس صورة، وإعادة تشغيل تلك الاستدعاءات كعوامل مسار ونص وتظليل في PDF هي ما يجعل النتيجة تتكيف مع الحجم كبقية الصفحة. تُعد THPDFPage.ShowMetafile ونظيرتها ShowMetafileEx نقطتي الدخول اللتين يستدعيهما التطبيق، وكلتاهما تسلّمان ملف التعريف إلى THPDFWmf، وهي الفئة التي تجتاز كل سجل GDI وتترجمه. التمييز ليس مطلقًا، ولا يدّعي HotPDF عكس ذلك: سجل ملف تعريف يمثل بيانات نقطية فعلية، مثل نسخ صورة نقطية عبر StretchDIBits، يُضمَّن ككائن صورة PDF XObject حقيقي عبر AddImage وShowImage، الزوج نفسه من الاستدعاءات الذي تمر به أي صورة أخرى على الصفحة، بدلًا من إجباره على عوامل مسار لا يمكنها التعبير عن صورة فوتوغرافية. الخطوط والتعبئات والنصوص تبقى متجهية؛ والبكسلات التي كانت بكسلات بالفعل في المصدر تبقى بكسلات في المخرجات. أبسط استدعاء لا يحتاج أكثر من ملف التعريف المحمَّل:
var
Pdf: THotPDF;
Chart: TMetafile;
begin
Pdf := THotPDF.Create(nil);
Chart := TMetafile.Create;
try
Chart.LoadFromFile('quarterly-revenue.emf'); // exported from TChart or GDI+
Pdf.FileName := 'quarterly-report.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafile(Chart);
Pdf.EndDoc;
finally
Chart.Free;
Pdf.Free;
end;
end;
كيف يحوّل المفسّر إحداثيات GDI إلى فضاء صفحة PDF؟
يجيب HotPDF عن ذلك بمرور واحد على تدفق سجلات ملف التعريف نفسه بدلًا من تنفيذ ثانٍ لـGDI. تقرأ THPDFWmf.Analyse ترويسة ملف التعريف عبر استدعاء Win32 المسمى GetEnhMetaFileHeader، وتعيد ضبط حالة الرسم الداخلية، وتستدعي EnumEnhMetafile، وهي واجهة التعداد البرمجية نفسها التي يستخدمها أي عارض ملفات تعريف، بحيث يصل كل سجل من نوع EMR_* إلى THPDFWmf.ExecuteRecord بالترتيب الذي سُجّل به أصلًا. يُعبّر GDI عن الإحداثيات من أعلى إلى أسفل بوحدات جهاز أو منطقية يختارها نمط التخطيط الخاص بملف التعريف نفسه؛ بينما صفحة PDF من أسفل إلى أعلى بنقاط فضاء المستخدم، وهو نظام الإحداثيات المشروح في نموذج رسم اللوحة في HotPDF للمسارات والتعبئات. تحل كل معالجة سجل هذا التعارض عبر ScaleX وScaleY، اللتان تستدعيان ProjectX وProjectY لإعادة تشغيل صيغة GDI الخاصة بتحويل النافذة إلى منفذ العرض لأنماط التخطيط اللامتناظرة والمتناظرة، بحيث يصل شكل مسجَّل بعرض خمس وحدات منطقية إلى العرض الصحيح بنقاط PDF بصرف النظر عن امتدادات النافذة ومنفذ العرض التي ضبطها التطبيق المصدر
كيف يتحول تدرج تعبئة GDI إلى نمط تظليل PDF؟
يتحول سجل EMR_GRADIENTFILL إلى نمط تظليل محوري حقيقي من النوع 2 في PDF (ISO 32000-1 §8.7.4.5) كلما سجّله GDI بأحد وضعي المستطيل. تقرأ THPDFWmf.VEMRGradientFill تخطيط السجل نفسه مباشرة من المخزن المؤقت للبايتات الخام، متبعةً بنية MS-EMF §2.3.1.6: مصفوفة رؤوس من ألوان RGBA بعمق 16 بت، تليها قائمة مستطيلات يشير كل منها إلى اثنين من تلك الرؤوس. بالنسبة لـGRADIENT_FILL_RECT_H، تنساب الألوان من اليسار إلى اليمين على طول الخط الأوسط الأفقي للمستطيل؛ وبالنسبة لـGRADIENT_FILL_RECT_V، تنساب من الأعلى إلى الأسفل على طول الخط الأوسط الرأسي. في كلتا الحالتين، يذهب لونا الزاويتين وإحداثيات المستطيل المسقطة مباشرة إلى THotPDF.RegisterAxialGradient، التي تُعيد اسم نمط، وترسم الصفحة المستطيل وتملؤه عبر ذلك النمط (SetFillPattern) بدلًا من استدعاء SetRGBFillColor مسطّح، بحيث يحتفظ ترويسة نمط جدول بيانات متدرجة الألوان أو منطقة رسم بياني متدرجة بمزجها بدلًا من الانهيار إلى لون واحد متوسط
وضع مثلثات جورو (Gouraud) هو الفجوة الصريحة. عندما يبلّغ حقل ulMode الخاص بالسجل عن GRADIENT_FILL_TRIANGLE، تتعرف VEMRGradientFill على ذلك، وتسجّل أن وضع المثلثات لم يُنفَّذ بعد، وتتخطى المستطيل بدلًا من تخمين تقريب ثنائي اللون. الاستيفاء لكل رأس ولكل بكسل عبر شبكة مثلثات عشوائية لا يمكن اختزاله إلى تظليل محوري أو شعاعي بمحطتين، والتعبير عنه بشكل صحيح يعني إصدار تظليل شبكي من النوع 4 أو 5 في PDF، وهي عائلة التظليل نفسها التي يتركها عارض الصفحات في HotPDF غير مرسومة أيضًا عند قراءة ملف PDF مرة أخرى. مساران مستقلان من الشيفرة يصلان إلى الحد الفاصل نفسه: تظليلات الشبكة هي الفجوة على جانبي الكتابة والقراءة معًا، ورسم مصدري يستخدم مثلثات جورو لتوهج شعاعي ناعم يعود إلى آخر فرشاة صلبة كانت مستخدمة، لا إلى تقريب مرسوم
فرش التظليل المتقاطع تصبح أنماط تبليط، لا رمادًا مسطحًا
تحتفظ فرشاة التظليل المتقاطع في GDI بنسيجها في PDF لأن THPDFWmf.SetBrushColor تتحقق من CurrentBrush.lbStyle بحثًا عن BS_HATCHED قبل أن تعود إلى تعبئة صلبة، وتوجّه تلك الحالة إلى SetHatchBrushPattern بدلًا من ذلك. تكتب تلك الدالة تدفق محتوى PDF بحجم 8×8 وحدات من عوامل رسم الخطوط، m وl وS، يُختار بحسب نمط التظليل في GDI: خط أفقي أو رأسي واحد لـHS_HORIZONTAL وHS_VERTICAL، وثلاثة أقطار متوازية لـHS_FDIAGONAL وHS_BDIAGONAL، والتوليفات الأفقية والرأسية أو القطرية المزدوجة لـHS_CROSS وHS_DIAGCROSS. تسجّل THotPDF.RegisterTilingPattern ذلك التدفق كنمط تبليط ملون (PaintType 1، ISO 32000-1 §8.7.3.1) بخطوتي XStep وYStep بحجم 8 وحدات، وتملأ الصفحة عبر SetFillPattern بالطريقة نفسها التي يعمل بها التظليل المحوري. مخطط أرضية CAD أو رسم هندسي يعتمد على تعبئات التظليل المتقاطع لتمييز المواد يحتفظ بتلك اللغة البصرية في PDF بدلًا من فقدان كل منطقة إلى رمادي متطابق
ليست كل فرشاة تستحق هذه المعاملة، والفجوة تستحق المعرفة قبل إصدار أي استيراد CAD. سجل EMR_CREATEDIBPATTERNBRUSHPT، وهو السجل الخاص بفرشاة نمط صورة نقطية مخصصة بدلًا من أحد أنماط التظليل الستة الجاهزة في GDI، يسجّل فقط مقبضه بحيث تبقى سجلات SELECTOBJECT وDELETEOBJECT اللاحقة متسقة؛ ولا يعرض HotPDF بعد خط أنابيب موارد نمط PDF لصور تبليط عشوائية، لذا فإن اختيار تلك الفرشاة يسقط إلى تعبئة احتياطية بلون صلب بدلًا من نسيج المصدر. إذا ظهرت تعبئة مسطّحة حيث استخدم الأصل بوضوح نسيج صورة متكرر، فإن فرشاة المصدر تكاد تكون فرشاة نمط DIB مخصصة بدلًا من تظليل متقاطع قياسي، وتلك هي الحالة الوحيدة الجديرة بالفحص اليدوي أولًا. إعداد استيراد لرسم كهذا لا يزال يمر عبر كائن الخيارات نفسه:
var
Pdf: THotPDF;
Drawing: TMetafile;
Options: THPDFEmfOptions;
begin
Pdf := THotPDF.Create(nil);
Drawing := TMetafile.Create;
Options := THPDFEmfOptions.Create;
try
Drawing.LoadFromFile('floor-plan.emf');
Options.Assign(Pdf.EmfOptions); // start from the document-wide defaults
Options.Redraw := False; // interpret the original EMF bytes, no GDI re-record pass
Options.ShowNullBrush := True; // keep explicitly unfilled CAD regions visible
Options.UseFrame := True; // clip output to the frame the EMF header declares
Pdf.FileName := 'floor-plan.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.ShowMetafileEx(Drawing, Options);
Pdf.EndDoc;
finally
Options.Free;
Drawing.Free;
Pdf.Free;
end;
end;
ما الذي يمنع ملف تعريف مشوّهًا من إفساد الصفحة؟
إجابة HotPDF هي بوابة واحدة في أعلى ExecuteRecord بدلًا من فحص دفاعي مكرر في كل من معالجات السجلات الثمانين تقريبًا. تُتابَع قوسا مسار GDI، اللذان يفتحهما EMR_BEGINPATH ويغلقهما EMR_ENDPATH أو EMR_ABORTPATH، عبر خاصية خاصة تُدعى PathContinue مدعومة بحقل FPathContinue. بينما ذلك القوس مفتوح، لا تسمح ExecuteRecord إلا بمرور سجلات بناء المسار، أي أنواع النقل والخط والخط المتعدد والمضلع ومنحنى بيزييه المتعدد وpolydraw، بالإضافة إلى CLOSEFIGURE ومجموعة صغيرة من سجلات التحويل وحالة سياق الجهاز مثل SETWORLDTRANSFORM وSAVEDC وRESTOREDC. أي نوع سجل آخر يصل إلى ExecuteRecord بينما القوس مفتوح، مثل EXTTEXTOUT شارد أو نسخ صورة نقطية، يُسقَط مركزيًا عبر Exit واحدة فور وصوله
توجد تلك البوابة لأن قوس مسار في ملف تعريف مكتوب يدويًا، أو منتج بأداة، أو ببساطة تالف، ليس مضمونًا أن يحتوي فقط على ما يضعه ملف سليم البنية بين سجلي الفتح والإغلاق. سجل إخراج نص يقع بين EMR_BEGINPATH وEMR_ENDPATH، بدون بوابة، إما أن يلوّث هندسة المسار قيد الإنشاء أو يُصدر عامل عرض نص PDF في منتصف تسلسل يُفترض أن يكون بناء مسار خالص، وكلا وضعي الفشل من النوع الذي يظهر على مُدخل واحد مشوَّه من أداة خارجية، لا على أي شيء تغطيه مجموعة اختبارات عادية عادةً. مركزة الفحص في ExecuteRecord تعني أن معالجات VEMR* الفردية لا تحتاج كل منها للدفاع عن استدعائها في الوقت الخاطئ؛ تقرر البوابة ذلك مرة واحدة، قبل التوزيع، بدلًا من ثمانين مرة بعده
وضع مخطط بياني متجهي بجانب النص والصور على صفحة واحدة
نادرًا ما تحتوي صفحة تقرير على مخطط بياني فقط، وتتركّب ShowMetafile مع عوامل صفحة HotPDF الأخرى تمامًا كأي استدعاء رسم آخر. يمكن لعنوان مرسوم بـTextOut، ومخطط أعمدة مملوء بتظليل متقاطع مستورد كـEMF، وشعار موضوع بـShowImage أن تجتمع كلها على الصفحة نفسها في تدفق المحتوى نفسه، محتفظًا كل منها بدقته الأصلية، وهو نمط التركيب المشروح في دليل HotPDF لتخطيط النص والخطوط والصور في تقرير:
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Q2 Regional Sales');
Pdf.CurrentPage.ShowMetafile(RegionChart); // hatch-filled bars, still vector
Pdf.CurrentPage.ShowImage(LogoIndex, 450, 760, 90, 30, 0);
مفسّر EMF وWMF، وأنماط التظليل المحوري التي يسجّلها لتعبئات التدرج، وربط نمط التبليط لفرش التظليل المتقاطع الموصوف هنا، تُشحن جميعها كجزء من مكوّن HotPDF القياسي لـDelphi وC++Builder، وهي مكتبة VCL أصلية بلا أي اعتماد على DLL خارجي لأي جزء من ذلك