مقاله فنی

واردات وکتور EMF در Free Pascal با PDFlibPas

PDFlibPas متافایل‌های enhanced را رکوردبه‌رکورد به محتوای واقعی صفحه PDF تبدیل می‌کند، نه به‌صورت شطرنجی، و همین است که یک نمودار یا نقشه CAD واردشده را در هر زوم شفاف نگه می‌دارد. آن مبدل حدود ۶۵۰۰ خط است و در برابر VCL نوشته شده بود، پس وقتی کتابخانه هدف Free Pascal پیدا کرد به‌عنوان غیرقابل انتقال طبقه‌بندی و با stub جایگزین شد. آن طبقه‌بندی غلط بود، و غلط بودنش درسی مفید درباره چگونگی ممیزی یک وابستگی پیش از تصمیم به بازنویسی پیرامون آن است

سطح واقعی VCL آن ۶۵۰۰ خط کوچک از آب درآمد: یک کلاس bitmap که برای فرمت پیکسل، ذخیره روی stream، هندل، canvas و scanlines‌هایش استفاده می‌شد؛ یک کلاس متافایل که برای عرض، ارتفاع و هندلش؛ و نوع رنگ با دو ثابت. هر یک از این‌ها از پیش توسط یونیت گرافیک خودِ کتابخانه فراهم می‌شد، که دقیقاً به همین وجود دارد تا بیلد non-VCL معادل‌ها را داشته باشد. مبدل اصلاً روی VCL گیر نداشت. روی یونیت Windows آزاد Free Pascal گیر داشت

بر محوری بشکنید که کد واقعاً به آن وابسته است

پس تغییر یک پیاده‌سازی دوباره نبود. یک شرط بود: از «کامپایل stub وقتی بدون VCL بیلد می‌شود» به «کامپایل stub وقتی برای Windows بیلد نمی‌شود». این محور درست است، و بیان اینکه چرا تفاوت را بدیهی می‌کند. یک متافایل enhanced یک ظرف Windows است. مبدل از اول تا آخر یک تجزیه‌گر برای رکوردهای GDI ویندوز است. اینکه برنامه میزبان از VCL استفاده می‌کند، از مجموعه ویجت دیگری، یا اصلاً از هیچ مجموعه ویجتی، هیچ ربطی به قابل تفسیر بودن آن رکوردها ندارد؛ اینکه هدف Windows است همه‌چیز با آن ربط دارد

پیامدهای انتخاب محور درست بی‌هزینه حاصل می‌شوند. بیلدهای C++Builder که نماد پلتفرم Windows را در این کتابخانه تعریف‌نمی‌کنند، stub پرتاب‌کننده استثنا را نگه می‌دارند و دقیقاً مثل قبل رفتار می‌کنند. macOS هم stub را نگه می‌دارد، به‌درستی، چون آنجا رکورد GDI برای تجزیه نیست. بیلدهای Delphi VCL دست‌نخورده می‌مانند. و بیلد Windows با مجموعه ویجت non-VCL به‌عنوان اثر جانبی واردات وکتوری EMF به دست می‌آورد، که کسی مجبور به پیاده‌سازی‌اش نبود. یک شرط هم‌راستا با وابستگی واقعی کار پلتفرم را به یک تغییر تک‌خطی تبدیل می‌کند؛ شرطی هم‌راستا با محور غلط آن را به بازنویسی‌ای تبدیل می‌کند که هرگز در برنامه قرار نمی‌گیرد

شرط واردات EMF از عضویت در VCL به پلتفرم Windows محورگیری دوباره می‌شود؛ جای دیگر stubها می‌مانند و بیلدهای non-VCL ویندوز واردات وکتوری می‌گیرند
محورگیری دوباره شرط stub به پلتفرم Windows همه رفتارهای بیلد موجود را نگه می‌دارد و به هدف‌های Windows غیر-VCL واردات وکتور EMF را بی‌هزینه می‌دهد

شکاف Free Pascal اعلان‌ها بود، نه منطق

آنچه واقعاً غایب بود اعلان‌های Win32 بود که یونیت Windows دلفی فراهم می‌کند و یونیت Free Pascal نه. جمع‌کردن آن‌ها در یک یونیت سازگاری واحد به‌جای پخش‌کردن شرط‌ها در سراسر مبدل، تجزیه‌گر را خوانا نگه داشت. فهرست آموزنده است چون نشان می‌دهد پوشش هدر میان دو RTL چقدر ناهموار است: ۱۱۳ ثابت نوع رکورد متافایل، دو پرچم خروجی متن توسعه‌یافته، سه ثابت حالت پرکردن گرادیان، یک نوع اشاره‌گر جدول هندل، نام‌های مستعار برای رکوردهای vertex و primitive گرادیان، و سه نوع رکورد که Free Pascal اصلاً اعلان نمی‌کند، پوشش‌دهنده alpha blending، blitting شفاف و حالت مدیریت رنگ

هیچ‌کدام از آن‌ها به‌تنهایی جذاب نیست. همه آن‌ها باید پیش از کامپایل تجزیه‌گر درست باشند، و یونیت سازگاری خانه طبیعی‌شان است چون می‌توان آن را به‌صورت یک یگان با مستندات هدر مقایسه کرد

اعلان‌های Win32 غایب از یونیت Windows Free Pascal، جمع‌شده در یک یونیت سازگاری برای مبدل وکتوری EMF به PDF
ثابت‌های رکورد، پرچم‌ها، نام‌های مستعار و سه نوع رکورد غایب همه در یک یونیت سازگاری می‌زنند که می‌توان با مستندات هدر مقایسه‌اش کرد

همان که بی‌سروصدا تصویر غلط می‌کشد

دو مورد از آن اعلان‌ها صرفاً غایب نیستند، حاضرند و برای این هدف غلط‌اند، و این بخشی است که حتی اگر هرگز متافایلی را لمس نکنید ارزش به‌یادسپردن دارد

Free Pascal رکورد ساخت قلم‌مو را با ساختار brush زمان اجرای تعبیه‌شده در آن اعلان می‌کند، و رکورد قلم توسعه‌یافته را با ساختار pen زمان اجرای تعبیه‌شده در آن. هر دو ساختار زمان اجرا عضو hatch خود را به‌صورت عدد صحیح هم‌اندازه اشاره‌گر اعلان می‌کنند، چون در فراخوانی زنده GDI آن عضو می‌تواند هندل حمل کند. یک متافایل اما همیشه فرم ۳۲بیتی را ذخیره می‌کند، چون چیدمان رکورد بخشی از فرمت فایل سریال‌شده است و با bitness فرایند تغییر نمی‌کند

در بیلدهای ۳۲بیتی این دو با هم موافق‌اند و اتفاقی نمی‌افتد. در Win64 عضو هم‌اندازه اشاره‌گر هشت بایت است در حالی که فایل چهار بایت دارد، پس هر فیلد پس از عضو hatch از آفست غلط خوانده می‌شود. نه استثنایی هست، نه خطای تجزیه و نه هشدار. متافایل فقط غلط رندر می‌شود: رنگ‌ها از بایت‌های غلط، پهنای قلم از بایت‌های غلط، و تصویری که شبیه یک باگ رندر است نه باگ چیدمان struct. دلفی دقیقاً به همین دلیل گونه‌های صراحتاً ۳۲بیتی هر دو ساختار را عرضه می‌کند، و یونیت سازگاری هم آن‌ها را به همان شکل دوباره اعلان می‌کند

چیدمان بایتی رکورد brush در EMF که نشان می‌دهد فیلد hatch هم‌اندازه اشاره‌گر فیلدهای بعدی را در Win64 چهار بایت جابه‌جا می‌کند در برابر چیدمان ثابت ۳۲بیتی
رکورد سریال‌شده همیشه یک hatch ۴ بایتی ذخیره می‌کند، پس ساختار زمان اجرای هم‌اندازه اشاره‌گر در Win64 همه فیلدهای بعدی را بی‌سروصدا غلط می‌خواند
// در Win64 غلط: Hatch هم‌اندازه اشاره‌گر است، فایل 32 بیت ذخیره می‌کند،
// و هر فیلد بعدی چهار بایت جابه‌جا می‌شود بدون هیچ خطا
type
  TLogBrushRuntime = record
    lbStyle: UINT;
    lbColor: COLORREF;
    lbHatch: ULONG_PTR;      // 8 بایت در یک فرایند 64بیتی
  end;

// درست: چیدمان سریال‌شده، عرض ثابت فارغ از bitness
type
  TLogBrush32 = record
    lbStyle: UINT;
    lbColor: COLORREF;
    lbHatch: DWORD;          // همیشه 4 بایت، همان‌طور که در متافایل ذخیره می‌شود
  end;

قاعده کلی: هر ساختاری که هم به‌عنوان آرگومان API زمان اجرا و هم به‌عنوان چیدمان فیلد سریال‌شده ظاهر می‌شود به دو اعلان نیاز دارد، و اعلان سریال‌شده باید سرتاسر از انواع با عرض ثابت استفاده کند. اعضای هم‌اندازه اشاره‌گر در یک فرمت فایل همیشه باگی‌اند که منتظر یک بیلد ۶۴بیتی است

تفاوت‌های امضای تابع در یک wrapper جا دارند، نه در هر call site

تفاوت‌های باقی‌مانده ناهمخوانی‌های معمولی امضای تابع بودند، و راه جذب آن‌ها یک wrapper ارجاعی است نه یک شرط در هر یک از call siteها. تابع ترکیب transform زیر Free Pascal اشاره‌گر می‌گیرد جایی که Delphi پارامتر مرجع می‌گیرد، پس wrapper مرجع می‌گیرد و آدرس پاس می‌دهد. همچنین هر دو آرگومان مبدأ را ابتدا در localها کپی می‌کند، چون مبدل call siteهایی دارد که ماتریس مقصد هم‌زمان یکی از مبدأهاست، و پاس‌دادن یک آدرس یکسان دو بار به تابعی که در همان حال نوشتن می‌خواند transformای تولید می‌کند که به‌شکل ظریفی غلط است، به شکلی که فقط روی محتوای چرخیده ظاهر می‌شود

function CombineTransformCompat(var Dest: TXForm;
  const A, B: TXForm): BOOL;
var
  SrcA, SrcB: TXForm;
begin
  // ابتدا کپی: فراخوانندگان به‌طور مشروع Dest را به‌جای A یا B پاس می‌دهند
  SrcA := A;
  SrcB := B;
{$IFDEF FPC}
  Result := Windows.CombineTransform(@Dest, @SrcA, @SrcB);
{$ELSE}
  Result := Windows.CombineTransform(Dest, SrcA, SrcB);
{$ENDIF}
end;

نوع‌های rectangle و point حالت دیگرند. Free Pascal رکوردهای rectangle و point متافایل را نوع‌هایی متمایز از نوع‌های گرافیکی عمومی می‌داند، پس هشت محل انتساب به یک cast صریح میان رکوردهای با چیدمان یکسان نیاز داشت. هر دو کامپایلر فرم cast را می‌پذیرند، پس آن محل‌ها هیچ شرطی حمل نمی‌کنند، که ارزش کمی زشتی دارد

این تغییر برای یک استقرار Free Pascal چه می‌کند

واردات وکتوری EMF روی Windows زیر Free Pascal کار می‌کند و همان محتوای صفحه بیلد دلفی را تولید می‌کند: pathها به‌عنوان path، گرادیان‌ها به‌عنوان محتوای pattern، و متن به‌عنوان متن. بیرون از Windows مسیر raster همچنان پاسخ است، و آن محدودیتی از فرمت است نه از پورت. وضعیت مختصات و clipping که مبدل به آن تغذیه می‌شود در مقاله ردیاب CTM و clipping جریان محتوا توصیف شده، و primitiveهای وکتوری که ساطع می‌کند در گرافیک وکتوری، شیدرها و گرادیان‌ها پوشش داده شده است

اگر دارید codebase خودتان را برای همین فرصت ممیزی می‌کنید، تمرین مفید همان است که این کار را شروع کرد: فهرست اعضایی که واقعاً از فریمورکی که فکر می‌کنید به آن وابسته‌اید استفاده می‌کنید را بنویسید. پاسخ اغلب بسیار کوتاه‌تر از آن است که فهرست import القا می‌کند، و قید واقعی معمولاً کلاً جای دیگری است. مسیرهای واردات مبتنی بر device context به‌طور کلی در مقاله پیش‌نمایش چاپ و device context توصیف شده‌اند، و پوشش پلتفرم و toolchain در صفحه محصول losLab PDF Developer Library فهرست شده است