مقاله فنی

پیکربندی PDFium: وقتی Brotli بی‌سروصدا Skia را سواپ می‌کند

در PDFium Component برای Delphi، روشن کردن BrotliEnabled یا IsolatePerDocument در TPdfLibraryConfiguration سابقاً build مربوط به Skia همراه‌شده را بدون هیچ خطایی به renderer یعنی AGG سواپ می‌کرد، چون هر دو گزینه FPDF_LIBRARY_CONFIG را به نسخه‌ای می‌برند که PDFium مقدار m_RendererType را عیناً می‌خواند. از v3.123.0 renderer پیش‌فرض همان پیش‌فرض خود DLL می‌ماند، و از v3.125.0 درخواست Skia یا Fontations ای که DLL نتواند جوابش را بدهد یک EPdfError قابل-گرفتن می‌دهد به‌جای کشتن پروسه

هیچ‌کدام از دو باگ خودش را اعلام نکرد. اولی صفحه‌هایی تولید می‌کرد که سالم به نظر می‌رسیدند، فقط با یک rasterizer دیگر رندر شده بودند، با anti-aliasing و لبه‌های متنی کمی متفاوت از build ای که شپ کرده و تست کرده بودید. دومی خودش را اعلام کرد، با صدای بلند، با انداختن پروسه میزبان از داخل مقداردهی اولیه بومی. هر دو از یک جا می‌آیند: یک ساختار C نسخه‌دار که فیلدهایش فقط وقتی می‌شمارند که شماره نسخه بگوید می‌شمارند، و مقدار صفرش هم «تنظیم‌نشده» نیست بلکه یک انتخاب واقعی است

‏FPDF_LIBRARY_CONFIG چطور تصمیم می‌گیرد PDFium از کدام renderer استفاده کند؟

‏FPDF_InitLibraryWithConfig فقط وقتی سراغ m_RendererType می‌رود که فیلد Version ساختار 4 یا بالاتر باشد، و از آن نسخه به بعد مقدار را عیناً همان‌طور که نوشته شده به کار می‌برد. زیر نسخه 4 PDFium فیلد را نادیده می‌گیرد و پیش‌فرض build را برمی‌دارد، که در buildهای کامپایل‌شده با PDF_USE_SKIA مقدار Skia است و بقیه جاها AGG

هر فیلد بعدی همان الگو را دنبال می‌کند. ساختار دانه‌دانه یک قابلیت در هر بار بزرگ شد، و هر قابلیت همراه یک شماره نسخه جدید آمد. PDFium Component ساختار بومی را در LoadLibrary از TPdfLibraryConfiguration شما می‌سازد و نسخه را فقط تا جایی بالا می‌برد که گزینه‌های ست‌شده شما لازم دارند

نسخه ساختارفیلدی که اضافه می‌کندست‌شده توسط
2m_pIsolate، m_v8EmbedderSlotهمیشه نوشته می‌شود؛ ‏V8Isolate، V8EmbedderSlot
3m_pPlatformوقتی V8Platform برابر nil نباشد
4m_RendererTypeوقتی Renderer چیزی جز prpDefault باشد
5m_FontLibraryTypeوقتی FontBackend چیزی جز pfbpDefault باشد
6m_BrotliEnabledوقتی BrotliEnabled = True
7m_IsolatePerDocumentوقتی IsolatePerDocument = True

تله در دو سطر آخر است. نسخه‌ها تجمعی‌اند: یک ساختار نسخه 6 هم یک ساختار نسخه 4 است هم نسخه 5، پس PDFium مقدار m_RendererType و m_FontLibraryType را می‌خواند حتی اگر شما فقط Brotli خواسته باشید. هر چه در آن لحظه در آن دو فیلد باشد renderer و font backend می‌شود، چه بخواهیدشان انتخاب کرده باشید چه نه

نمودار نردبان نسخه FPDF_LIBRARY_CONFIG در PDFium Component از نسخه 2 تا نسخه 7 که نشان می‌دهد کدام گزینه TPdfLibraryConfiguration مقدار m_RendererType و m_FontLibraryType و m_BrotliEnabled و m_IsolatePerDocument را اضافه می‌کند، و چرا نسخه‌های تجمعی روی هر buildی فیلد renderer صفرشده را به یک انتخاب عمدی AGG تبدیل می‌کنند نه یک مقدار تنظیم‌نشده
هر گزینه نسخه ساختار را بالا می‌برد و هر فیلد قبلی زنده می‌ماند، پس صفر داخل m_RendererType به‌شکل یک درخواست صریح AGG به PDFium می‌رسد

چرا روشن کردن Brotli renderer را به AGG می‌برد؟

قبل از v3.123.0، PDFium Component برای prpDefault مقدار FPDF_RENDERERTYPE_AGG را داخل m_RendererType می‌نوشت، پس هر پیکربندی‌ای که ساختار را به نسخه 6 یا 7 می‌رساند AGG را روی یک build مبتنی بر Skia تحمیل می‌کرد. ‏runtimeهای pdfium.dll و pdfium.v8.dll که با کامپوننت شپ می‌شوند buildهای Skia هستند، پس این مستقیم به deployment پیش‌فرض می‌خورد، نه به یک حالت عجیب‌وغریب

آن مپ کردن موقع نوشتن بی‌ضرر به نظر می‌رسید. در نسخه 2 یا 3 فیلد هرگز خوانده نمی‌شود، پس prpDefault واقعاً یعنی «هر چه DLL انجام می‌دهد». لحظه‌ای که BrotliEnabled (نسخه 6) یا IsolatePerDocument (نسخه 7) وارد ماجرا شد، همان کد «بی‌ترجیح» را به یک درخواست صریح AGG تبدیل می‌کرد. هیچ چیز شکست نمی‌خورد. PDFium عادی مقداردهی اولیه می‌شد، هر صفحه را رندر می‌کرد، و هیچ کد خطایی برنمی‌گرداند، چون از دید خودش فراخواننده AGG خواسته بود و AGG گرفته بود

یک hash پیکسلی سواپ را جایی آشکار می‌کند که اسکرین‌شات نمی‌کند. رندر صفحه اول همان سند نمونه زیر سه پیکربندی این نتیجه را داد:

  • پیکربندی پیش‌فرض: مقدار hash برابر 502D77C3711B4ACF
  • ‏BrotliEnabled = True با رها کردن Renderer روی prpDefault: مقدار hash برابر F75B5EB4728ADE87
  • ‏prpAgg صریح: مقدار hash برابر F75B5EB4728ADE87، عین اجرای Brotli

فیکس در v3.123.0 تابع عمومی PdfNativeRendererType است که یک TPdfRendererPreference را به مقداری که در m_RendererType نوشته می‌شود حل می‌کند. ‏prpAgg و prpSkia یک-به-یک مپ می‌شوند. ‏prpDefault حالا وقتی DLL بارگذاری‌شده مقدار FPDF_RenderPageSkia را export کند Skia مپ می‌شود و بقیه حالت‌ها AGG. آن export زیر همان شرط PDF_USE_SKIA کامپایل می‌شود که خود پیش‌فرض Skia، که آن را به تنها ویژگی buildی تبدیل می‌کند که از بیرون DLL قابل مشاهده است. بعد از فیکس پیکربندی Brotli همان hash پیکربندی پیش‌فرض را تولید می‌کند

نمودار مقایسه hash پیکسلی در PDFium Component که hash رندر Skia پیش‌فرض یعنی 502D77C3711B4ACF را نشان می‌دهد، پیکربندی BrotliEnabled قبل از v3.123.0 که با یک اجرای صریح prpAgg با hash یعنی F75B5EB4728ADE87 برابر بود، و wrapper فیکس‌شده که prpDefault را از طریق export یعنی FPDF_RenderPageSkia به hash اصلی Skia برمی‌گرداند
یک hash پیکسلی چیزی را می‌گیرد که اسکرین‌شات قایم می‌کند: روشن کردن Brotli سابقاً هر صفحه را با AGG رندر می‌کرد، و پیش‌فرض فیکس‌شده حالا با پیکربندی دست‌نخورده جور است

font backend هرگز همین مشکل را نداشت. مقدار m_FontLibraryType از نسخه 5 به بعد خوانده می‌شود، و مقدار صفرش یعنی FPDF_FONTBACKENDTYPE_FREETYPE هم همان پیش‌فرض PDFium است وقتی فیلد اصلاً خوانده نشود. پس نوشتن FreeType برای pfbpDefault پیش‌فرض بومی را دقیقاً بازتولید می‌کند. مقادیر صفر همیشه غلط نیستند، فقط هرگز خودکار درست نیستند

با v3.123.0 یا بعدتر، کد راه‌اندازی‌ای که به‌طور طبیعی می‌نویسید حالا همان را می‌کند که می‌گوید:

uses
  PDFium;

procedure ConfigurePdfiumAtStartup;
var
  Config: TPdfLibraryConfiguration;
begin
  // باید قبل از هر چیزی که کتابخانه بومی را load می‌کند اجرا شود
  Config := TPdfLibraryConfiguration.Default;
  Config.BrotliEnabled := True;   // مقدار FPDF_LIBRARY_CONFIG را به نسخه 6 می‌برد
  // مقدار Renderer روی prpDefault می‌ماند: روی buildهایی که export می‌کنند
  // مقدار FPDF_RenderPageSkia به Skia و روی buildهای فقط-AGG به AGG حل می‌شود
  SetLength(Config.UserFontPaths, 1);
  Config.UserFontPaths[0] := 'C:\ProgramData\MyApp\Fonts';
  ConfigurePdfLibrary(Config);
end;

یادتان باشد BrotliEnabled فقط وقتی streamهای /BrotliDecode مربوط به PDF 2.0 را قابل-رمزگشایی می‌کند که خود DLL با PDF_ENABLE_BROTLI ساخته شده باشد. فلگ یک درخواست است، و روی buildی بدون پشتیبانی Brotli هیچ اثری ندارد. مقدار TPdfLibraryConfiguration.Hardened همان Default است جز اینکه AllowMachineTime برابر False است، که خواندن ساعت واقعی را از JavaScript سند می‌بندد؛ نقطه شروع معقولی برای پردازش سمت-سرور فایل‌های نامطمئن است

وقتی backend ای را درخواست کنید که DLL ندارد چه می‌شود؟

PDFium برای renderer یا font backend ای که در build نیست خطا برنمی‌گرداند: ‏FPDF_InitLibraryWithConfig یک CHECK بومی را شکست می‌دهد، که در Windows به‌شکل یک breakpoint exception بیرون می‌زند و بدون یک structured exception handler دور فراخوانی، پروسه را خاتمه می‌دهد. هدر همین را می‌گوید، هشدار می‌دهد که یک مقدار پشتیبانی‌نشده «به همین شکل با یک crash فوری شکست می‌خورد»

دو حالت ملموس یک build فقط-AGG هستند که FPDF_RENDERERTYPE_SKIA می‌گیرد، و یک build بدون Fontations که FPDF_FONTBACKENDTYPE_FONTATIONS می‌گیرد. ‏runtime Skia همراه‌شده در گروه دوم است: با Skia رندر می‌کند اما برای فونت‌ها از FreeType استفاده می‌کند. درخواست prpSkia همراه pfbpFontations مقابل آن در سمت دلفی مقدار External exception 80000003 تولید می‌کرد. وقتی دیباگر یا یک exception handler اتفاقاً آن را بگیرد، وضعیت همچنان غیرقابل-بازیابی است:

  • PDFium نیمه-مقداردهی-اولیه رها می‌شود
  • پیکربندی در-سطح-پروسه از قبل مهر شده، پس ConfigurePdfLibrary از پیکربندی اصلاح‌شده سر باز می‌زند
  • تلاش دوباره با پیکربندی متفاوت در همان پروسه دیگر ممکن نیست

این همان شکست معکوس باگ Brotli است. آنجا فیلد مقداری را حمل می‌کرد که هیچ‌کس انتخابش نکرده بود و PDFium بی‌سروصدا می‌پذیرفتش. اینجا فیلد مقداری را حمل می‌کند که فراخواننده عمداً انتخابش کرده و PDFium هیچ بحثی سرش نمی‌کند. هر دو مشکلاتی‌اند که یک wrapper باید قبل از فراخوانی بومی حلشان کند، چون بعدش چیزی برای گرفتن نمانده

PDFium Component چطور Skia و Fontations را پیش‌چک می‌کند

از v3.125.0 به بعد، LoadLibrary پیکربندی را بعد از bind شدن exportهای DLL و قبل از صدا زدن FPDF_InitLibraryWithConfig اعتبارسنجی می‌کند، و یک renderer یا font backend پشتیبانی‌نشده را به یک EPdfError با پیامی تبدیل می‌کند که تنظیم متخلف و جایگزین‌ها را نام می‌برد. DLL unload می‌شود و پیکربندی بازمهر می‌شود، پس فراخواننده می‌تواند تنظیمات دیگری بردارد و دوباره load کند

خود تصمیم در تابع خالص PdfLibraryConfigurationSupportError زندگی می‌کند، که پیکربندی به‌علاوه دو Boolean را می‌گیرد که build را توصیف می‌کنند و وقتی ترکیب امن است یک رشته خالی برمی‌گرداند. چون هیچ state بومی را لمس نمی‌کند، می‌توانید از تست‌های خودتان با هر ترکیب قابلیت صداش کنید. داخل LoadLibrary آن دو Boolean از انواع مختلف شواهد می‌آیند و سطح اعتماد متفاوتی می‌ارزند:

  • Skia از حضور export یعنی FPDF_RenderPageSkia تشخیص داده می‌شود، همان سیگنالی که PdfNativeRendererType استفاده می‌کند. export و renderer مربوط به Skia زیر یک شرط کامپایل می‌شوند، پس چک دقیق است
  • Fontations export مخصوص خودش را ندارد. تنها ردی که می‌گذارد crateهای فونت Rust است که داخل باینری می‌کشد، پس PDFium Component فایل کتابخانه بارگذاری‌شده را برای نام‌های crate یعنی skrifa و read-fonts (و read_fonts) اسکن می‌کند. اسکن فقط وقتی pfbpFontations درخواست شده اجرا می‌شود، و فایلی که نشود خواند «بدون Fontations» حساب می‌شود

چک Fontations یک heuristic است و می‌تواند در یک جهت غلط دربیاید: یک build مبتنی بر Fontations که از هر یک از آن رشته‌ها لخت شده باشد رد می‌شود حتی اگر می‌توانست کار کند. آن معامله عمداً بسته شد. یک رد غلط هزینه‌اش یک exception است که می‌توانید بگیرید و یک fallback به FreeType. یک پذیرش غلط هزینه‌اش پروسه است

باز-مهر شدن به اندازه خود چک مهم است. ‏LoadLibrary پیکربندی را همان اول load مهر می‌کند، پس بدون reset یک رد قابلیت ConfigurePdfLibrary را می‌گذاشت به هر تلاش دوباره‌ای با EPdfError جواب می‌داد «پیکربندی کتابخانه PDFium از قبل مهر شده است». مسیر رد اول UnloadLibrary را صدا می‌زند؛ فراخوانی FPDF_DestroyLibrary اش در آن نقطه امن است چون PDFium هنوز مقداردهی اولیه نشده و بلافاصله برمی‌گردد. بقیه شکست‌های load، مثل DLL مفقود یا ناهمخوانی معماری، مهر را نگه می‌دارند، پس یک حلقه تلاش-مجدد باید این دو را از هم تشخیص بدهد:

uses
  SysUtils, PDFium;

function StartPdfiumPreferringSkia: TPdfRendererPreference;
var
  Config: TPdfLibraryConfiguration;
begin
  Config := TPdfLibraryConfiguration.Default;
  Config.Renderer := prpSkia;
  ConfigurePdfLibrary(Config);
  try
    PDFium.LoadLibrary;   // واجد-یونیت: مقدار Windows.LoadLibrary هم‌نام است
    Result := prpSkia;
  except
    on E: EPdfError do
    begin
      // یک رد قابلیت DLL را unload و پیکربندی را بازمهر می‌کند.
      // DLL ای که اصلاً load نشد مهرشده می‌ماند: تلاش دوباره فایده ندارد
      if PdfLibraryConfigurationSealed then
        raise;
      Config.Renderer := prpAgg;
      ConfigurePdfLibrary(Config);
      PDFium.LoadLibrary;
      Result := prpAgg;
    end;
  end;
end;

به PDFium.LoadLibrary صریح توجه کنید. در یونیتی که هم از Windows یا Winapi.Windows استفاده می‌کند، یک LoadLibrary بدون واجد-یونیت به هر یونیتی حل می‌شود که در clauses استفاده‌ها آخر آمده؛ وقتی آن تابع Win32 است، فراخوانی بدون-آرگومان با یک خطای تعداد-آرگومان کامپایل نمی‌شود که هیچ چیزی درباره PDFium نمی‌گوید

نمودار جریان پیش‌چک LoadLibrary در PDFium Component که ConfigurePdfLibrary پیکربندی را مهر می‌کند، چک قابلیت export یعنی FPDF_RenderPageSkia و شواهد رشته skrifa را تست می‌کند، یک درخواست پشتیبانی‌نشده یک EPdfError قابل-گرفتن می‌دهد و برای تلاش دوباره بازمهر می‌کند، در حالی که DLL ای که هرگز load نمی‌شود مقدار PdfLibraryConfigurationSealed را true نگه می‌دارد
اعتبارسنجی بعد از bind شدن exportها و قبل از مقداردهی اولیه اجرا می‌شود، پس backend غایب به‌شکل یک EPdfError قابل-گرفتن شکست می‌خورد نه یک CHECK بومی که پروسه را می‌کشد

اعتبارسنجی که حتی زودتر می‌افتد

مقدار ConfigurePdfLibrary بعضی ترکیب‌ها را قبل از اینکه اصلاً DLLی در ماجرا باشد رد می‌کند، همه با EPdfError. یک FontBackend صریح، از جمله pfbpFreeType، Renderer = prpSkia می‌خواهد، چون PDFium فقط برای renderer مربوط به Skia سراغ font backend می‌رود. ‏IsolatePerDocument می‌خواهد V8Isolate برابر nil باشد، چون PDFium به‌ازای هر سند isolate خودش را می‌سازد و اگر شما هم یکی دستش بدهید یک CHECK بومی را شکست می‌دهد. رشته‌های خالی در UserFontPaths رد می‌شوند. و هر فراخوانی بعد از اولین تلاش load با «پیکربندی کتابخانه PDFium از قبل مهر شده است» شکست می‌خورد

آن قاعده آخر یک نتیجه عملی دارد: نمی‌توانید اول DLL را probe کنید و بعدش پیکربندی‌اش کنید. ‏GetSkiaRenderCapabilities و V8FeaturesAvailable و باز کردن یک سند و بیشتر بقیه نقاط ورود در درون LoadLibrary را صدا می‌زنند، که پیکربندی را همان‌جا مهر می‌کند. صدا زدن UnloadLibrary بعداً هم آن را دوباره باز نمی‌کند. اول پیکربندی کنید، بعد load کنید، بعد سؤال بپرسید، که دقیقاً ترتیبی است که یک روتین عیب‌یابی باید دنبال کند:

uses
  SysUtils, PDFium, FPdfView;

function DescribePdfiumState: string;
var
  Config: TPdfLibraryConfiguration;
  Renderer: string;
begin
  Config := GetPdfLibraryConfiguration;   // یک کپی، امن برای بازرسی
  if not PDFium.Loaded then
  begin
    if PdfLibraryConfigurationSealed then
      Exit('PDFium failed to load; configuration is sealed');
    Exit('PDFium not loaded; configuration can still change');
  end;
  // همان حل‌وتقلیبی که LoadLibrary موقع ساختن FPDF_LIBRARY_CONFIG اعمال کرد
  if PdfNativeRendererType(Config.Renderer,
    GetSkiaRenderCapabilities.PageRender) = FPDF_RENDERERTYPE_SKIA then
    Renderer := 'Skia'
  else
    Renderer := 'AGG';
  Result := Format('Renderer=%s Brotli=%s IsolatePerDocument=%s',
    [Renderer, BoolToStr(Config.BrotliEnabled, True),
     BoolToStr(Config.IsolatePerDocument, True)]);
end;

لاگ کردن آن یک خط در راه‌اندازی ارزان است، و اولین چیزی است که در یک تیکت پشتیبانی می‌خواهید که می‌گوید «متن روی سرور متفاوت به نظر می‌رسد». ‏PDFium.Loaded به همان دلیل LoadLibrary واجد-یونیت است: داخل یک متد فرم یا کامپوننت، یک Loaded لخت به TComponent.Loaded bind می‌شود

دو راهی که یک ساختار پیکربندی C نسخه‌دار خراب می‌شود

هر ساختار پیکربندی نسخه‌دار، چه FPDF_LIBRARY_CONFIG باشد چه یک رکورد cbSize مربوط به Win32 چه یک ABI افزونه، به دو شکل متقارن شکست می‌خورد، و یک wrapper باید در مقابل هر دو نگهبانی کند. اولی پر کردن یک فیلد در حالی که نسخه خیلی پایین مانده؛ دومی بالا بردن نسخه در حالی که یک فیلد روی مقدار صفر مانده که کتابخانه آن را یک انتخاب عمدی می‌خواند

  1. فیلد ست شده، نسخه خیلی پایین. مقدار m_BrotliEnabled = 1 را داخل یک ساختار نسخه 2 بنویسید و PDFium هرگز سراغش نمی‌رود. فراخوانی موفق می‌شود و streamهای Brotli قابل-رمزگشایی نمی‌مانند. دفاع، استنتاج نسخه از فیلدهایی است که واقعاً در استفاده‌اند، که همان کاری است که LoadLibrary می‌کند، نه هاردکد کردن یکی
  2. نسخه به‌اندازه کافی بالا، فیلد صفر یعنی چیزی. نسخه را به 6 برسانید و هر فیلدی تا نسخه 6 حالا زنده است. مقدار FillChar مقدار m_RendererType را صفر می‌کند که یعنی FPDF_RENDERERTYPE_AGG، یک renderer واقعی، نه «تنظیم‌نشده». دفاع این است که هر فیلدی که نسخه انتخابی پوشش می‌دهد با یک مقدار عمدی نوشته شود، و «پیش‌فرض» مقابل build واقعی حل شود نه اینکه فرضش کنید

برای مقادیری که می‌توانند callee را کرش کنند یک قاعده سوم دنبال می‌شود: قبل از فراخوانی مقابل چیزی که باینری می‌تواند انجام دهد اعتبارسنجی‌شان کنید، با قوی‌ترین شواهد موجود، و در کد و مستندات صادق باشید وقتی آن شواهد یک heuristic است. یک نماد exportشده اثبات است. یک نام crate در جدول رشته‌ها یک حدس خوب

مرجع سریع: پیکربندی کتابخانه در PDFium Component

  • مقدار ConfigurePdfLibrary را یک بار، قبل از هر چیزی که DLL را load می‌کند، صدا بزنید؛ هر کوئری قابلیت یا load سندی آن را مهر می‌کند
  • اگر BrotliEnabled یا IsolatePerDocument را ست می‌کنید و از runtimeهای همراه‌شده انتظار خروجی Skia دارید به v3.123.0 یا بعدتر ارتقا بدهید
  • مقدار Renderer را روی prpDefault رها کنید مگر اینکه rasterizer خاصی لازم داشته باشید؛ حالا در هر نسخه ساختاری به پیش‌فرض build حل می‌شود
  • از PdfNativeRendererType با GetSkiaRenderCapabilities.PageRender استفاده کنید تا لاگ کنید کدام renderer واقعاً فعال است
  • برای prpSkia روی یک DLL فقط-AGG یا pfbpFontations روی یک DLL بدون-Fontations در v3.125.0 یا بعدتر انتظار EPdfError داشته باشید، نه crash
  • بعد از یک رد قابلیت، مقدار PdfLibraryConfigurationSealed برابر False است و می‌توانید دوباره پیکربندی کنید؛ بعد از یک load ناموفق DLL همچنان True می‌ماند
  • تشخیص Fontations را heuristic بگیرید و یک fallback به FreeType نگه دارید
  • مقدارهای PDFium.LoadLibrary و PDFium.Loaded را با نام یونیت بنویسید تا از برخورد نام با Win32 و TComponent در امان باشید

اگر DLL قبل از اینکه پیکربندی اصلاً اهمیت پیدا کند شکست بخورد، از عیب‌یابی شکست‌های load مربوط به DLL یعنی PDFium در دلفی شروع کنید، و برای اینکه کامپوننت روی هر پلتفرم چطور باینری درست را پیدا می‌کند load کردن کتابخانه بومی PDFium روی هر target را ببینید. وقتی renderer سر و مر شد، کش رندر و تاکتیک‌های زوم روان پوشش می‌دهد چطور رندر صفحه را در یک viewer سریع نگه دارید

‏PDFium Component موتور PDFium را برای Delphi و C++Builder با چک‌های پیکربندی مثل این‌ها wrap می‌کند، تا مقداردهی اولیه بومی به‌شکل یک exception در پاسکال شکست بخورد که می‌توانید هندلش کنید نه یک خروج پروسه. جزئیات محصول و دانلودها در صفحه محصول PDFium Component برای Delphi است