مقاله فنی

وارد کردن برداری EMF و WMF به PDFهای Delphi با HotPDF

HotPDF، کامپوننت بومی PDF برای Delphi و C++Builder، متافایل‌های EMF و WMF ویندوز را با تفسیر مستقیم هر رکورد GDI به عملگرهای PDF وارد می‌کند، به‌جای اینکه فایل را به یک بیت‌مپ صاف کند: پرشدگی‌های گرادیانی به الگوهای سایه‌زنی محوری PDF تبدیل می‌شوند، قلم‌موهای هاشور به الگوهای کاشی‌کاری PDF تبدیل می‌شوند، و یک دروازه‌ی متمرکز وضعیت مسیر مانع از آن می‌شود که رکوردهای بدشکل خروجی را خراب کنند. هر نموداری که یک TChart، یک سطح GDI+، یا یک TCanvas ساده بتواند به‌صورت یک متافایل تقویت‌شده export کند، کاندیدای این مسیر است، و تفاوت آن همان لحظه‌ای نمایان می‌شود که کسی روی صفحه زوم کند یا آن را به یک چاپگر با وضوح بالا بفرستد

گزینه‌ی جایگزینی که اغلب توسعه‌دهندگان Delphi به‌طور پیش‌فرض به سراغش می‌روند، rasterize کردن متافایل به یک بیت‌مپ پیش از قرار دادن آن روی صفحه است، و هزینه‌اش تنها بعداً نمایان می‌شود: نموداری میله‌ای که روی صفحه‌نمایش تیز بود، همان لحظه‌ای که PDF با ۶۰۰ DPI چاپ شود یا روی صفحه‌ی سالن جلسه پروجکت شود، به‌وضوح بلوکی می‌شود، و یک ناحیه‌ی CAD پر از هاشور، اگر سبک پرشدگی منتقل نشود، به یک مستطیل خاکستری تخت و یکنواخت فرومی‌ریزد. خواندن متافایل به‌عنوان یک برنامه به‌جای یک تصویر، همان کاری است که از هر دو مشکل جلوگیری می‌کند، و مسیر سخت‌تری برای پیاده‌سازی درست آن است، به همین دلیل دام‌های زیر پیش از انتشار یک گزارش ارزش دانستن دارند

چرا یک متافایل را تفسیر کنیم به‌جای صاف‌کردنش به بیت‌مپ؟

HotPDF وارد کردن EMF و WMF را روی مسیر برداری نگه می‌دارد چون یک متافایل ویندوز یک دنباله‌ی ضبط‌شده از فراخوانی‌های ترسیم GDI است، نه یک تصویر، و بازپخش آن فراخوانی‌ها به‌صورت عملگرهای مسیر، متن، و سایه‌زنی PDF چیزی است که اجازه می‌دهد نتیجه مثل بقیه‌ی صفحه مقیاس‌پذیر بماند. THPDFPage.ShowMetafile و همتای آن ShowMetafileEx نقاط ورودی‌ای هستند که یک برنامه فراخوانی می‌کند، و هر دو متافایل را به THPDFWmf، کلاسی که هر رکورد GDI را پیمایش کرده و ترجمه می‌کند، می‌سپارند. این تفکیک مطلق نیست، و HotPDF هم وانمود نمی‌کند که مطلق است: رکوردی از متافایل که واقعاً داده‌ی رستری است، مثلاً یک blit بیت‌مپ از نوع StretchDIBits، به‌عنوان یک PDF Image 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 را فرا می‌خواند، همان API شمارشی که یک نمایشگر متافایل استفاده می‌کند، پس هر رکورد EMR_* به همان ترتیبی که در ابتدا ضبط شده به THPDFWmf.ExecuteRecord می‌رسد. GDI مختصات را از بالا به پایین در واحدهای دستگاهی یا منطقی که با mapping mode خودِ متافایل انتخاب شده بیان می‌کند؛ یک صفحه‌ی PDF از پایین به بالا در نقاط فضای کاربر است، همان سیستم مختصاتی که در مدل ترسیم canvas در HotPDF برای مسیرها و پرشدگی‌ها پوشش داده شده. هر handler رکورد این ناهماهنگی را از طریق ScaleX و ScaleY حل می‌کند، که ProjectX و ProjectY را فرا می‌خوانند تا فرمول window-to-viewport خودِ GDI را برای mapping modeهای anisotropic و isotropic بازپخش کنند، پس شکلی که با عرض پنج واحد منطقی ضبط شده، صرف‌نظر از اینکه برنامه‌ی منبع چه اندازه‌ی window و viewport را تنظیم کرده، در عرض درست از نقاط PDF می‌نشیند

یک پرشدگی گرادیانی GDI چطور به یک الگوی سایه‌زنی PDF تبدیل می‌شود؟

یک رکورد EMR_GRADIENTFILL هرگاه GDI آن را در یکی از دو حالت مستطیلی ضبط کرده باشد، به یک الگوی سایه‌زنی محوری واقعی از نوع ۲ در PDF تبدیل می‌شود (ISO 32000-1 §8.7.4.5). THPDFWmf.VEMRGradientFill چیدمان خود رکورد را مستقیماً از بافر بایت خام می‌خواند، با پیروی از ساختار MS-EMF §2.3.1.6: یک آرایه‌ی رأسی از گوشه‌های RGBA شانزده‌بیتی، به‌دنبال آن فهرستی از مستطیل‌ها که هرکدام به دو تا از آن رأس‌ها ارجاع می‌دهند. برای GRADIENT_FILL_RECT_H، رنگ‌ها از چپ به راست در امتداد خط میانی افقی مستطیل جاری می‌شوند؛ برای GRADIENT_FILL_RECT_V، از بالا به پایین در امتداد خط میانی عمودی جاری می‌شوند. در هر دو حالت، دو رنگ گوشه و مختصات مستطیل تصویرشده مستقیماً به THotPDF.RegisterAxialGradient می‌روند، که یک نام الگو برمی‌گرداند، و صفحه مستطیل را رسم کرده و آن را از طریق آن الگو پر می‌کند (‏SetFillPattern) به‌جای یک فراخوانی تخت SetRGBFillColor، پس یک هدر باندی به‌سبک صفحه‌گسترده یا ناحیه‌ی نمودار گرادیانی یک چارت، درهم‌آمیختگی رنگش را حفظ می‌کند به‌جای اینکه به یک رنگ میانگین فروبریزد

حالت مثلث Gouraud شکاف صادقانه‌ای است. وقتی فیلد ulMode رکورد GRADIENT_FILL_TRIANGLE را گزارش دهد، VEMRGradientFill آن را تشخیص می‌دهد، ثبت می‌کند که حالت مثلثی هنوز پیاده‌سازی نشده، و به‌جای حدس زدن یک تقریب دورنگی، از آن مستطیل صرف‌نظر می‌کند. درون‌یابی رأس‌به‌رأس و پیکسل‌به‌پیکسل روی یک مش مثلثی دلخواه، به یک سایه‌زنی محوری یا شعاعی دو-توقفی تقلیل نمی‌یابد، و بیان درست آن یعنی صادرکردن یک سایه‌زنی مشی از نوع ۴ یا ۵ در PDF، همان خانواده‌ی سایه‌زنی‌ای که رندرر صفحه‌ی HotPDF هم هنگام خواندن یک PDF بی‌رنگ رها می‌کند. دو مسیر کد بی‌ربط به هم روی یک مرز یکسان فرود می‌آیند: سایه‌زنی‌های مشی هم در سمت نوشتن و هم در سمت خواندن شکاف هستند، و یک نمودار منبع که از مثلث‌های Gouraud برای یک درخشش شعاعی نرم استفاده می‌کند، به هرچه آخرین قلم‌موی توپر بوده برمی‌گردد، نه به یک تقریب رندرشده

قلم‌موهای هاشور به الگوهای کاشی‌کاری تبدیل می‌شوند، نه خاکستری صاف‌شده

یک قلم‌موی هاشور GDI بافتش را در PDF حفظ می‌کند، چون THPDFWmf.SetBrushColor پیش از هر بازگشتی به یک پرشدگی توپر، CurrentBrush.lbStyle را برای BS_HATCHED بررسی می‌کند و آن حالت را به SetHatchBrushPattern هدایت می‌کند. آن متد یک جریان محتوای PDF ۸ در ۸ واحدی از عملگرهای خط رسم‌شده می‌نویسد، 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 هشت‌واحدی ثبت می‌کند، و صفحه از طریق SetFillPattern همان‌طور که یک سایه‌زنی محوری پر می‌شود پر می‌شود. یک نقشه‌ی طبقه‌ی CAD یا یک نقشه‌ی مهندسی که برای تمایزگذاری مواد به پرشدگی‌های هاشور تکیه می‌کند، آن زبان بصری را در PDF حفظ می‌کند به‌جای اینکه هر ناحیه به یک خاکستری یکسان تقلیل یابد

هر قلم‌مویی این رفتار را نصیبش نمی‌شود، و این شکاف پیش از انتشار یک واردسازی CAD ارزش دانستن دارد. EMR_CREATEDIBPATTERNBRUSHPT، رکورد مربوط به یک قلم‌موی الگوی تصویر بیت‌مپی سفارشی به‌جای یکی از شش سبک هاشور استاندارد GDI، فقط handle خودش را ثبت می‌کند تا رکوردهای بعدی SELECTOBJECT و DELETEOBJECT سازگار بمانند؛ HotPDF هنوز یک خط لوله‌ی منبع Pattern در 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 است، نه یک بررسی دفاعی که در هر یک از حدود هشتاد handler رکوردش تکرار شود. یک براکت مسیر GDI، که با EMR_BEGINPATH باز و با EMR_ENDPATH یا EMR_ABORTPATH بسته می‌شود، توسط یک ویژگی خصوصی به نام PathContinue که با فیلد FPathContinue پشتیبانی می‌شود، پیگیری می‌شود. تا زمانی که آن براکت باز است، ExecuteRecord فقط اجازه می‌دهد رکوردهای ساخت مسیر عبور کنند، انواع move، line، polyline، polygon، polybezier، و polydraw، به‌علاوه CLOSEFIGURE و مجموعه‌ای کوچک از رکوردهای تبدیل و وضعیت DC مانند SETWORLDTRANSFORM، SAVEDC، و RESTOREDC. هر نوع رکورد دیگری که تا زمان باز بودن براکت به ExecuteRecord برسد، مثلاً یک EXTTEXTOUT سرگردان یا یک blit بیت‌مپ، همان لحظه‌ی رسیدن با یک Exit تکی به‌صورت متمرکز کنار گذاشته می‌شود

این دروازه به این دلیل وجود دارد که یک براکت مسیر در یک متافایل دستی‌نوشته، ابزارساخته، یا صرفاً خراب‌شده، تضمینی ندارد که فقط چیزی را در بر بگیرد که یک فایل درست‌ساخت‌شده بین رکوردهای باز و بسته‌اش می‌گذارد. یک رکورد خروجی متن که بین EMR_BEGINPATH و EMR_ENDPATH فرود بیاید، بدون یک دروازه، یا هندسه‌ی مسیر در حال ساخت را آلوده می‌کند یا یک عملگر نمایش متن PDF را در میانه‌ی دنباله‌ای صادر می‌کند که قرار است ساخت مسیر خالص باشد، و هر دو حالت خرابی از نوعی هستند که روی یک ورودی بدشکل از یک ابزار شخص ثالث نمایان می‌شوند، نه چیزی که یک مجموعه تست معمولی معمولاً پوشش دهد. متمرکزکردن این بررسی در ExecuteRecord یعنی هر یک از handlerهای تکی VEMR* نیازی ندارد جداگانه در برابر فراخوانی‌شدن در زمان اشتباه از خودش دفاع کند؛ دروازه این را یک‌بار، پیش از dispatch، تصمیم می‌گیرد، نه هشتاد بار بعد از آن

قرار دادن یک نمودار برداری کنار متن و تصاویر روی یک صفحه

یک صفحه‌ی گزارش به‌ندرت فقط یک نمودار دارد، و 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 خارجی برای هیچ‌کدام از این‌ها