مقاله فنی

HotPDF روی Free Pascal و Lazarus: محدودیت‌های Win64

پاسخ کوتاه به آن تیکت پشتیبانی بله است، با چند محدودیت. HotPDF 2.730.0 روی Free Pascal 3.2.2 و Lazarus 4.6 برای Win64 بیلد می‌شود و مسیرهای اصلی ساخت و لود و ذخیره کار می‌کنند. آن‌چه دنبالش نمی‌آید هر چیزی است که روی یک آبجکت کدک نیتیو لینک‌شده ایستا یا روی متدهای بی‌نام Delphi تکیه کند

این پرسش معمولاً به یک شکل می‌رسد: تیمی برای یک ابزار چندسکویی روی Lazarus استاندارد می‌شود، یا یک کدبیس Free Pascal به ارث می‌برد، و همان کامپوننت PDF‌ای را می‌خواهد که از قبل برای Delphi لایسنس کرده. پورت کردن یک کتابخانه بالغ Delphi به‌ندرت مسئله سینتکس است. بخش جالب این است که پورت کجا لو می‌دهد کتابخانه بی‌سروصدا به یک زنجیره ابزار چسبیده بوده؛ و در این مورد چسبیدگی در دو جای خیلی خاص نشسته: ABI فایل‌های آبجکت کدک‌های همراه، و قابلیت‌های کامپایلری که پشت یک سیمبل نسخه پنهان شده‌اند

یک ماتریس قابلیت که بیلد Delphi را با بیلد Win64 روی Free Pascal 3.2.2 و Lazarus 4.6 مقایسه می‌کند و نشان می‌دهد کدام مسیرهای سند مشترک‌اند و کدام APIهای کدک و فشرده‌سازی و رندر موازی و متد بی‌نام به یک استاب استثناپرتاب می‌رسند
مسیرهای اصلی ساخت و لود و ذخیره در هر دو بیلد یکسان‌اند و شکاف تماماً در کدک‌های لینک‌شده ایستا و APIهای متد بی‌نام است

آن‌چه Free Pascal 3.2.2 پیش از کامپایل HPDFDoc لازم دارد

HotPDF زیر Free Pascal فقط در حالت Delphi کامپایل می‌شود و فقط وقتی که شاخه‌های یونیت LCL لازاروس روی مسیر جستجو باشند. هیچ‌کدام قابل مذاکره نیست. HotPDF.inc داخل بلاک {$IFDEF FPC} خودش کامپایلر را با {$MODE DELPHI} و {$H+} سوییچ می‌کند و با یک {$FATAL} هر چیز قدیمی‌تر را رد می‌کند وقتی FPC_FULLVERSION زیر 30202 است، پس نصب 3.0.x با سر و صدا شکست می‌خورد به‌جای اینکه یونیت خراب تولید کند. پکیج رانتایم لازاروس HotPDFLaz.lpk باقی را کدگذاری می‌کند: LCL به‌عنوان پکیج الزامی و -Mdelphi به‌عنوان یک آپشن سفارشی

الزام LCL کسانی را غافلگیر می‌کند که فقط خروجی کنسول می‌خواهند، اما این الزام ساختاری است. HPDFFPCCompat تایپ‌های VCL دلفی را که Free Pascal معادلی برایشان ندارد تأمین می‌کند؛ TMetafile و TMetafileCanvas را روی کلاس‌های بیت‌مپ و کنوس LCL نگاشت می‌کند و TRichEdit را به TMemo مستعاری می‌کند، و HPDFDoc هم TPNGObject را به Graphics.TPortableNetworkGraphic مستعاری می‌کند. این‌ها را شیم‌های زمان کامپایل ببینید، نه برابری قابلیت: یک کلاس متافایل با پشتوانه بیت‌مپ یونیت را در حال کامپایل نگه می‌دارد، اما رفتار مسیرهای متافایل را مثل Delphi نمی‌کند. حتی اسموک‌تست غیر گرافیکی هم Interfaces را درگیر می‌کند و اسکریپت بیلد -Fu را برای lcl\units\x86_64-win64 و شاخه خروجی lazutils پاس می‌دهد

چرا D2009+ نمی‌تواند نقش گیت نسخه را هم بازی کند

وسوسه‌کننده است که بیلد Free Pascal را یک کامپایلر مدرن بگیرید و سیمبل جدیدترین قابلیت دلفی را ساده تعریف کنید. HotPDF این کار را نمی‌کند و دلیلش ارزش گفتن صریح دارد: D2009+ فقط یعنی رشته‌های یونیکد نیست، گیت یونیت‌هایی هم هست که API عمومی‌شان با متدهای بی‌نام بیان شده. Free Pascal 3.2.2 نه متدهای بی‌نام دلفی را پشتیبانی می‌کند نه آن APIها را، پس قرض گرفتن سیمبل کدی را وارد می‌کند که اصلاً کامپایل نمی‌شود. بنابراین بند uses در HPDFDoc دو دنباله شرطی جداگانه حمل می‌کند و هم‌پوشانی میان آن‌ها عمدی است نه تصادفی

uses
  // ...
  HPDFJavaScript,
  HPDFFormCalcGraph
{$IFDEF FPC}
  , HPDFFPCCodecStubs,
  HPDFCMS,
  HPDFWinCertSigner
{$ENDIF}
{$IFDEF D2009+}
  , HPDFXFARuntime,
  HPDFCMS,
  HPDFWinCertSigner,
  HPDFSignVerify,
  HPDFSignatureBatch
{$ENDIF};

چرا کدک‌های نیتیو در لینکر متوقف می‌شوند؟

چون آن‌ها آبجکت‌های COFF ویندوز ۶۴ بیتی هستند که توسط یک زنجیره ابزار خاص تولید شده‌اند و هیچ‌کدام از دو لینکر Free Pascal روی Win64 حاضر به خوردنشان نیست: نه لینکر داخلی، نه مسیر GNU ld خارجی. این یک مشکل ABI فایل آبجکت است نه یک مشکل پاسکال، و هیچ مقدار سورس شرطی آن را درست نمی‌کند. کتابخانه تنها مسیر صادقانه موجود را برمی‌دارد. هر دایرکتیو {$L} که یک آبجکت کدک ایستا را وارد می‌کند در {$IFNDEF FPC} پیچیده شده، پس بیلد Free Pascal آن‌ها را ساده حذف می‌کند و HPDFFPCCodecStubs بعداً هر سیمبل خارجی گم‌شده را به‌شکل استابی فراهم می‌کند که به‌جای برگشتن استثنا پرتاب می‌کند

// HPDFFPCCodecStubs.pas
function HPDFFPCNativeCodecUnavailable: PtrUInt;
begin
  raise ENotSupportedException.Create(
    'This native codec is not available in the Free Pascal build');
end;

function HPDFFPCStub_deflate: PtrUInt; cdecl;
  public name 'deflate';
begin
  Result := HPDFFPCNativeCodecUnavailable;
end;

آن جدول استاب طولانی است و خواندنش دقیقاً به شما می‌گوید امروز کدام قابلیت‌ها فقط مخصوص Delphi هستند: نقطه‌های ورود deflate با zlib-ng و zopfli، فشرده‌سازی و ازفشرده‌سازی libjpeg، کدک JPEG 2000 در OpenJPEG، libtiff و مقداردهی‌های اولیه به ازای هر فشرده‌سازیش، انکد و دیکد JBIG2، نقطه‌های ورود تبدیل رنگ Little-CMS و پریمیتیوهای AES. انتخاب طراحی پشت استاب‌ها از خود فهرست مهم‌تر است. یک سیمبل گم‌شده در زمان لینک دیواری از ارجاع‌های تعریف‌نشده از یونیتی می‌دهد که هرگز به آن دست نزده‌اید؛ اما استابی که ENotSupportedException پرتاب کند بیلدی می‌دهد که اجرا می‌شود، پیامی که دلیل را نام می‌برد و استک‌تریسی که به محل فراخوانی اشاره می‌کند. یعنی بیلد Free Pascal هرگز جایی که بیلد Delphi بایت‌های درست تولید می‌کرد، بی‌سروصدا بایت اشتباه تولید نمی‌کند. اثر مرتبه دوم را هم ببینید: اجرای کدک‌های تصویر غیرقابل‌اعتماد در یک پروسس جدا تصمیمی است که فقط در بیلد Delphi پیش می‌آید، چون بیلد Free Pascal اصلاً دیکدر نیتیو درون‌پروسسی برای سندباکس کردن ندارد

در Delphi آبجکت‌های کدک ایستای HotPDF لینک می‌شوند و به‌صورت نیتیو اجرا می‌شوند، اما بیلد Win64 در Free Pascal دایرکتیوهای لینک را رد می‌کند و هر سیمبل خارجی گم‌شده را به استابی می‌فرستد که در محل فراخوانی یک استثنای نام‌دار پرتاب می‌کند
رد کردن دایرکتیوهای لینک و استاب کردن هر سیمبل خارجی، دیوار ارجاع‌های تعریف‌نشده را به بیلدی تبدیل می‌کند که اجرا می‌شود و محدودیت‌هایش را خودش نام می‌برد

فشرده‌سازی: اولین خطی که باید عوض شود cmNone است

پیش از پورت هر چیز دیگری، Compression را روی cmNone بگذارید. THPDFCompressionMethod دقیقاً دو مقدار دارد، cmNone و cmFlateDecode، و مورد دوم مستقیم به نقطه‌های ورود deflate می‌رود که در بیلد Free Pascal استاب‌اند. اول مدل آبجکت اصلی را با فشرده‌سازی خاموش راستی‌آزمایی کنید، بعد تصمیم بگیرید چه چیز دیگری لازم دارید. ترتیب کار در اسموک‌تست همراه محصول همین است: یک سند تک‌صفحه‌ای فشرده‌نشده بسازید، دوباره لودش کنید و تأیید کنید شمار صفحه‌ها یک برگشته. خروجی فشرده‌نشده بزرگ‌تر است و همچنان یک PDF کاملاً معتبر است

program HotPDFLazarusSmoke;

{$mode delphi}
{$H+}

uses
  Interfaces, SysUtils, HPDFDoc;

var
  Pdf, Reloaded: THotPDF;
  OutputFile: string;
  PageCount: Integer;
begin
  OutputFile := IncludeTrailingPathDelimiter(GetTempDir) +
    'HotPDF-FPC-Smoke.pdf';
  Pdf := THotPDF.Create(nil);
  try
    Pdf.FileName := OutputFile;
    Pdf.Compression := cmNone;   // cmFlateDecode به یک سیمبل استاب‌شده می‌رسد
    Pdf.BeginDoc;
    Pdf.CurrentPage.SetFont('Arial', [], 12);
    Pdf.CurrentPage.TextOut(72, 72, 0, 'HotPDF Free Pascal smoke test');
    Pdf.EndDoc;
  finally
    Pdf.Free;
  end;

  Reloaded := THotPDF.Create(nil);
  try
    PageCount := Reloaded.LoadFromFile(OutputFile);
    if PageCount <> 1 then
      raise Exception.CreateFmt('Expected one page, got %d', [PageCount]);
  finally
    Reloaded.Free;
  end;
end.

با رندر موازی صفحه‌ها چه می‌شود؟

هنوز کامپایل می‌شود، هنوز بیت‌مپ‌های درست برمی‌گرداند و دیگر موازی نیست. THotPDF.RenderLoadedPagesParallel و THotPDF.RenderLoadedPagesParallelOrdered روی TThread.CreateAnonymousThread با یک کلوژر procedure درون‌خطی بنا شده‌اند که Free Pascal 3.2.2 نمی‌تواند بیانش کند، پس شاخه Free Pascal یک فالبک سریال قطعی اجرا می‌کند: ایندکس صفحه‌ها را به ترتیب می‌رود، برای هرکدام RenderLoadedPageToBitmap را صدا می‌زند و موفقیت‌ها را می‌شمارد. شکل API، مقدار برگشتی و آرایه خروجی تغییری نکرده‌اند و همین است که اجازه می‌دهد یک کدبیس واحد هر دو جور بیلد شود

همان فراخوانی رندر موازی HotPDF در Delphi روی نخ‌های کارگر هم‌پوشان اجرا می‌شود و در Free Pascal ایندکس صفحه‌ها را سریال طی می‌کند، و رکورد info پایپ‌لاین به‌جای پنهان کردن فالبک، شمار کارگر یک را گزارش می‌دهد
شاخه Free Pascal شکل API و آرایه خروجی را نگه می‌دارد و شمار کارگر یک را گزارش می‌کند، پس کدی که رکورد info را می‌خواند حقیقت را می‌بیند
var
  Bitmaps: THPDFBitmapArray;
  Info: THPDFParallelRenderPipelineInfo;
  Rendered: Integer;
begin
  Rendered := Pdf.RenderLoadedPagesParallel([0, 1, 2, 3], 150, 4,
    Bitmaps, Info);
  // Delphi: ‏Info.WorkerCount هر چیزی است که بودجه حافظه اجازه می‌داد
  // Free Pascal: ‏Info.WorkerCount همیشه 1 است، صفحه‌ها به ترتیب ایندکس
  if Info.WorkerCount = 1 then
    LogSerialFallback(Rendered, Info.RequestedWorkerCount);

فالبک بی‌سروصدا نیست و همین بخش ارزش طراحی حولش را دارد. THPDFParallelRenderPipelineInfo را صادقانه پر می‌کند: PageCount از درخواست، RequestedWorkerCount بازتاب همان که خواسته‌اید، WorkerCount روی 1، و شمارهای completed و delivered هم‌خوان با آن‌چه واقعاً برگشته. کدی که از قبل Info را برای اندازه گرفتن نوار پیشرفت یا بودجه حافظه بازرسی می‌کند سر جایش کار می‌کند و حقیقت را می‌خواند نه یک فرض را. اگر برنامه توان‌عبوری شما به پایپ‌لاین رندر موازی و مدل backpressure آن وابسته است، آن برنامه یک برنامه Delphi است؛ روی Free Pascal بودجه‌تان را برای هزینه تک‌نخی رندر یک صفحه به بیت‌مپ ضربدر شمار صفحه‌ها ببندید

عملاً کدام بیلد را عرضه می‌کنید؟

بر اساس قابلیت انتخاب کنید، نه بر اساس سلیقه. اگر گردش کار شما سرهم‌بندی سند، متن و رسم برداری، پر کردن فرم، لود و ذخیره است، بیلد Free Pascal روی Win64 آن را پوشش می‌دهد و بهتر است با فشرده‌سازی خاموش راستی‌آزمایی کنید پیش از روشن کردن هر چیز. اگر کارتان تصویر JPEG یا JPEG 2000 یا TIFF یا JBIG2 دارد، تبدیل‌های رنگ ICC، خروجی فشرده یا توان‌عبوری وابسته به هسته‌های زیاد، فعلاً روی Delphi یا C++Builder بمانید. مرز را یک ABI فایل آبجکت و یک قابلیت زبانی غایب کشیده‌اند؛ هر دو در سورس قابل دیدن‌اند تا مدفون در یک ماتریس پشتیبانی، و هر دو با یک خطای نام‌دار شکست می‌خورند نه با یک نتیجه غلط

پکیج Free Pascal و Lazarus در همان توزیع یونیت‌های Delphi و C++Builder عرضه می‌شود، پس یک لایسنس هر دو را پوشش می‌دهد و می‌توانید مسیر لازاروس را روی اسناد خودتان آزمایش کنید پیش از متعهد شدن به آن؛ صفحه محصول HotPDF Delphi PDF Component ماتریس پشتیبانی کامپایلر به‌روز و مرجع کامل API را دارد