مقاله فنی

ذخیره XFA در PDFium Component: خط جدید، emoji و restoreState

‏PDFium Component مقدارهای ویرایش‌شده فرم XFA را عیناً ذخیره می‌کند، در طول ذخیره و باز کردن دوباره، وقتی runtime یعنی pdfium.v8.dll مربوط به Windows V8 را اجرا کند که در v3.125.2 یا بعدتر شپ شده. ‏runtimeهای قدیمی‌تر به مقدار فیلدها line feed اضافه می‌کردند، emoji را به یک کاراکتر BMP بی‌ربط می‌بُریدند، ذخیره‌های XFA تک-stream را بی‌سروصدا رد می‌کردند و می‌توانستند یک نوشتن نهایی ناموفق را قورت بدهند. یک symptom مربوط به باز کردن دوباره اصلاً نقص کتابخانه نیست: فرم پویایی که subform ریشه‌اش restoreState="auto" ندارد layout خود را از template بازسازی می‌کند

باگ‌ریپورت‌های این ماجرا همه شبیه هم بودند. مشتری یک فرم ادعای XFA را در یک viewer دلفی پر می‌کند، ذخیره می‌کند، دوباره باز می‌کند، و یک چیزی کمی جای دیگری است. جعبه کامنت‌های خالی حالا یک خط خالی دارد، و بعد از یک ذخیره دوم دو تا. اسمی که با یک emoji تایپ شده با یک گلیف private-use برمی‌گردد. هیچ‌کس خطا نمی‌گیرد، و همین است که این باگ‌ها را پرهزینه می‌کند: drift چند هفته بعد در خروجی somebody دیگر بیرون می‌زند

وقتی یک فرم XFA ذخیره و دوباره باز می‌شود چه چیزی خراب می‌شود؟

چهار نقص جداگانه در مسیر ذخیره بومی XFA باعث drift مقدارها می‌شد، و هر کدام پشت یک ذخیره موفق-به-نظر-رسنده قایم می‌شد. دو تاشان از serialization می‌آمدند، یکی از چیدمان ذخیره‌سازی تک-stream، و یکی از خود PDF writer. جدول هر symptom را به علتش و به نسخه‌ای که PDFium Component فیکسش کرد مپ می‌کند

Symptom بعد از reopenعلتفیکس‌شده در
فیلد خالی یک line feed دارد؛ مقدارها با هر ذخیره یک خط جدید می‌گیرندهر دو نویسنده XFA بعد از تگ‌های شروع خط جدید چیدمانی درج می‌کردندv3.125.2، ‏pdfium.v8.dll
مقدار U+1F642 به‌شکل U+F642 برمی‌گردد، یا emoji از پاکت فرم حذف می‌شودبرش wchar_t شانزده‌بیتی در رمزگشایی؛ فیلتر surrogate در serializer فرمv3.125.2، ‏pdfium.v8.dll
ویرایش‌ها در یک سند XFA تک-stream کلاً ناپدید شده‌اندذخیره بومی چیدمان stream را رد می‌کرد، اما مقدار برگشتی نادیده گرفته می‌شدv3.125.2؛ کامنت‌ها و processing instructionها از v3.126.0 حفظ می‌شوند
فایل بریده‌شده با اینکه ذخیره موفقیت گزارش کرده بودنوشتن بافری نهایی بعد از اینکه نویسنده از قبل موفقیت گزارش کرده بود شکست خورد‏runtime V8 در v3.125.2؛ ‏pdfium.dll معمولی در v3.125.3
فرم پویای سه‌صفحه‌ای به‌شکل دو صفحه باز می‌شودsubform ریشه مقدار restoreState="auto" را درخواست نمی‌کندنویسندگی فرم، نه نقص کتابخانه

نوشته‌های قبلی نتیجه می‌گرفتند ویرایش‌های فیلد XFA اصلاً با PDFium قابل پایدارسازی نیستند، که برای runtimeهای آن روزها درست بود. ‏runtime جدیدتر V8 مقدارهای XFA را به‌طور بومی ذخیره می‌کند، پس ویرایشی که در فرم زنده انجام شده بدون جراحی پاکت از سمت شما به پاکت datasets ذخیره‌شده می‌رسد

کدام runtime یعنی PDFium مقدارهای XFA را ذخیره می‌کند؟

وفاداری ذخیره XFA به DLL بومی وابسته است نه به wrapper دلفی، پس اولین چک این است که پروسه شما واقعاً کدام runtime را load کرده. PDFium Component به‌ازای هر معماری دو build برای Windows شپ می‌کند: مقدار pdfium.dll معمولی که بدون V8 و XFA ساخته شده، و pdfium.v8.dll که موتور JavaScript و runtime فرم XFA را حمل می‌کند. فقط pdfium.v8.dll می‌تواند یک فرم XFA را اجرا کند، پس هر فیکس XFA این مقاله همان‌جا زندگی می‌کند، از کتابخانه‌های V8 بازسازی‌شده Win32 و Win64 در v3.125.2 به بعد

فیکس نوشتن-نهایی کد عمومی PDF writer است، پس برای اسناد معمولی هم مهم می‌شود. ‏v3.125.3 کتابخانه‌های pdfium.dll معمولی را بازسازی کرد تا همان ترمیم را حمل کنند. سورس مشترک اثبات رفتار مشترک نیست: تا وقتی باینری بازسازی نشده، DLL قدیمی باگ قدیمی را نگه می‌دارد

تله دوم در loader بود. قبل از v3.125.2، ست کردن EnableV8Engine روی True باعث می‌شد binding نام پیش‌فرض یعنی pdfium.v8.dll را بردارد و یک مسیر کامل در LibraryName را نادیده بگیرد. برنامه‌ای که به یک runtime تازه-deployشده اشاره می‌کرد می‌توانست همچنان یک کپی قدیمی‌تر را از پوشه دیگری load کند. از v3.125.2 به بعد، یک LibraryName که درونش پوشه باشد در هر دو حالت موتور دقیقاً همان فایل را انتخاب می‌کند، و مسیر مفقود به‌جای fallback به کتابخانه همراه دیگر شکست می‌خورد

uses
  System.SysUtils, PDFium;

procedure SelectXfaRuntime;
begin
  // یک مسیر در LibraryName همین فایل را پین می‌کند (v3.125.2 و بعدتر)؛
  // اگر فایل نباشد، load کردن raise می‌کند به‌جای fallback
{$IFDEF WIN64}
  PDFium.LibraryName := ExtractFilePath(ParamStr(0)) + 'DLLs\Win64\pdfium.v8.dll';
{$ELSE}
  PDFium.LibraryName := ExtractFilePath(ParamStr(0)) + 'DLLs\Win32\pdfium.v8.dll';
{$ENDIF}
  PDFium.EnableV8Engine := True;
  PDFium.LoadLibrary;  // در راه‌اندازی شکست بخور، نه در اولین ذخیره
end;

بعد از باز کردن یک سند، مقدار TPdf.XFA به شما می‌گوید فایل XFA دارد و TPdf.XfaRuntimeAvailable می‌گوید DLL بارگذاری‌شده واقعاً می‌تواند اجرایش کند. اگر هم لازم است فرم‌های استاتیک و پویا را تفکیک کنید، مقدار TPdf.FormType برمی‌گرداند ftXfaFull یا ftXfaForeground؛ مقاله تشخیص فرم‌های XFA و استخراج پاکت‌های XFA در دلفی همان سرک کشیدن را با جزئیات پوشش می‌دهد

چرا فیلدهای XFA ذخیره‌شده line feed اضافه می‌گیرند؟

فیلدهای XFA ذخیره‌شده line feed می‌گرفتند چون هر دو نویسنده XFA بومی، نویسنده المان XML عمومی و serializer پاکت فرم، خروجی‌شان را با یک خط جدید بعد از تگ‌های شروع قشنگ-چاپ می‌کردند. در بیشتر XML ها آن فاصله سفید تزئینی است. در داده XFA نیست: وقتی پاکت datasets دوباره parse می‌شود، متن بین <Comments> و </Comments> همان مقدار فیلد است، با خط جدید. پس یک فیلد خالی با یک LF منفرد دوباره باز می‌شد، و هر چرخه ذخیره-و-باز-کردن بعدی می‌توانست یکی دیگر اضافه کند

نمودار چرخه ذخیره XFA در PDFium Component که در آن نویسنده بعد از تگ‌های شروع خط جدید اضافه می‌کند، parser دوباره-بازشده مقدار LF بین تگ‌های Comments را به‌عنوان مقدار فیلد می‌خواند، و هر ذخیره بعدی یک line feed دیگر اضافه می‌کند تا v3.125.2 فقط فاصله سفید سنتز‌شده توسط serializer را حذف می‌کند
یک چرخه ذخیره-باز کردن دوباره اولین line feed را می‌کارد و هر دور بعدی یکی دیگر اضافه می‌کند، و برای همین drift شکل کاملش را فقط در نسل دوم نشان داد

ترمیم واضح یعنی کوتاه کردن مقدارها موقع load، غلط بود. کاربرها فاصله‌های ابتدایی و انتهایی و متن چند-خطی عمدی داخل فیلدهای XFA تایپ می‌کنند، و یک بلاک آدرس یا یک کد با-عرض-ثابت باید بایت به بایت جان سالم به در ببرد. پس فیکس v3.125.2 فقط آن فاصله سفیدی را حذف می‌کند که خود serializer دور تگ‌ها سنتز کرده بود. مقدارهای کاربر و گره‌های متنی موجود و بخش‌های CDATA دست‌نخورده عبور می‌کنند، پس مقدار " indented" تورفتگی‌اش را نگه می‌دارد و فیلدی که عمداً خالی است خالی می‌ماند

چرا یک emoji به‌شکل یک کاراکتر متفاوت برمی‌گردد؟

یک emoji اشتباه برمی‌گشت چون مقدار wchar_t مربوط به Windows 16 بیت عرض دارد، و دو مسیر رمزگشایی یک مقدار اسکالر کامل Unicode را داخل یک wchar_t منفرد ذخیره می‌کردند. decoder مربوط به stream یعنی UTF-8 و parser ارجاع‌های کاراکتر عددی مثل &#x1F642; هر دو همین کار را می‌کردند. مقدار U+1F642 یعنی صورت لبخند-ملایم در 16 بیت جا نمی‌شود، پس بیت‌های بالا می‌افتادند و U+F642 ظاهر می‌شد: یک code point در Private Use Area که بیشتر فونت‌ها آن را یک جعبه یا هیچ رندر می‌کنند

serializer فرم مشکل معکوس داشت. کاراکترها را یکی‌یکی به اندازه یک wchar_t فیلتر می‌کرد، دو code unit یعنی surrogate را می‌دید که در انزوا نامعتبرند، و هر دو را می‌انداخت، پس emoji کلاً از پاکت فرم محو می‌شد. در v3.125.2 decoder هر مقدار اسکالر را کامل مصرف می‌کند و یک جفت surrogate درست تولید می‌کند. وقتی فقط یک جای خروجی مانده باشد، surrogate پایین را معوق نگه می‌دارد و تا وقتی آن واحد هنوز بافر است end-of-stream گزارش نمی‌کند. یک دنباله UTF-8 که بین بلوک‌های خواندن بریده شود به خواندن بعدی منتقل می‌شود به‌جای دور انداخته شدن. صادرکننده فرم حالا جفت‌های surrogate معتبر را با هم نگه می‌دارد، و ارجاع‌های کاراکتر عددی هم جفت‌های درست تولید می‌کنند

نمودار مدیریت surrogate در PDFium Component که در آن مقدار U+1F642 به‌شکل جفت UTF-16 یعنی D83D و DE42 می‌رسد و دو مسیر نقص‌دار آن را خراب می‌کنند: decoderهای wchar_t شانزده‌بیتی اسکالر را در Private Use Area به U+F642 می‌بُرند، در حالی که serializer فرم surrogateهای تنها را فیلتر می‌کند و emoji را کلاً می‌اندازد
مقدار wchar_t در Windows 16 بیت عرض دارد، پس اسکالری که به یک جفت surrogate نیاز دارد یا نیمه بالایش را از دست می‌داد یا از پاکت محو می‌شد تا وقتی که هر دو مسیر یاد گرفتند جفت‌ها را با هم نگه دارند

داده تستی Latin-1 هیچ‌کدام از این‌ها را نشان نمی‌دهد، پس هر تست round-trip مربوط به XFA حداقل به یک کاراکتر از صفحه مکمل نیاز دارد

XFA تک-stream و شکست‌های ذخیره که هیچ‌کس نمی‌دید

یک سند XFA تک-stream ویرایش‌هایش را از دست می‌داد چون helper ذخیره بومی آن چیدمان ذخیره‌سازی را رد می‌کرد و فراخواننده‌اش شکست را نادیده می‌گرفت. ‏ISO 32000-1 §12.7.8 اجازه می‌دهد دریه /XFA دیکشنری فرم تعاملی یا یک آرایه از نام پاکت‌ها و streamها باشد یا یک stream منفرد که کل سند XDP را حمل می‌کند. آرایه‌های پاکت حالت رایج‌اند، اما streamهای منفرد کاملاً قانونی‌اند، و ذخیره PDF انگار هیچ اتفاقی نیفتاده تمام می‌شد در حالی که داده فرم روی مقادیر قدیمی‌اش مانده بود

از v3.125.2 به بعد، runtime یعنی V8 زیرمجموعه پشتیبانی‌شده تک-stream را هندل می‌کند. اول هر دو پاکت زنده یعنی datasets و form را به یک ناحیه stage برده و اعتبارسنجی می‌کند، و فقط بعد از آن پاکت‌های هم‌خوان را در XDP اصلی جایگزین می‌کند. بقیه پاکت‌ها و اعلان‌های namespace ریشه حفظ می‌شوند. اگر staging شکست بخورد، stream یعنی XFA پایدار هرگز لمس نمی‌شود و سند علامت ویرایشش را نگه می‌دارد

کامنت‌های XML و processing instructionها مراقبت اضافه می‌خواستند چون DOM داخلی XML آن‌ها را می‌اندازد. در v3.125.2 حضورشان باعث می‌شد ذخیره کلاً شکست بخورد به‌جای اینکه محتوا بی‌صدا از دست برود. ‏v3.126.0 آن‌ها را حفظ می‌کند: قبل از parse، هر کامنت یا processing instruction با یک marker ساخته‌شده از یک پیشوند سواپ می‌شود که هیچ‌جای متن اصلی رخ نمی‌دهد. بعد از جایگزینی پاکت‌های زنده، هر marker باید دقیقاً یک بار ظاهر شود قبل از اینکه token اصلی برگردد و stream نوشته شود. tokenهای بیرون پاکت‌های جایگزین‌شده پس متن و ترتیب خودشان را نگه می‌دارند، از جمله tokenها در prolog و template و بقیه پاکت‌ها

بعضی ورودی‌ها همچنان عمداً رد می‌شوند، و هر رد یک شکست ذخیره صریح است:

  • کامنت‌ها یا processing instructionها داخل پاکت‌های زنده یعنی datasets یا form، چون موقعیت‌های اصلی‌شان را نمی‌شود به محتوای تازه-صادرشده مپ کرد
  • اعلان‌های DTD و امضاهای XMLDSig، چون بازنویسی XDP نمی‌تواند یک امضای XML را معتبر نگه دارد
  • رمزگذاری نامعتبر UTF-8 یا UTF-16 و تگ‌های ناقص و ارجاع‌های کاراکتر نامعتبر و entityهای ناشناخته و processing instructionهای بدفرم، که به‌جای ترمیم بی‌صدا رد می‌شوند
نمودار خط‌لوله ذخیره XFA تک-stream در PDFium Component که در آن پاکت‌های زنده datasets و form به stage برده و اعتبارسنجی و بعد داخل XDP اصلی با حفظ کامنت‌ها از طریق markerها جایگزین می‌شوند، در حالی که شکست‌های staging و ورودی‌هایی مثل DTD یا XMLDSig ذخیره را صریحاً رد می‌کنند
خروجی stage‌شده قبل از هر جایگزینی اعتبارسنجی می‌شود، پس یک ذخیره ناموفق stream یعنی XFA پایدار را دست‌نخورده می‌گذارد و سند علامت ویرایشش را نگه می‌دارد

خروجی تک-stream یعنی UTF-8 است و مدل محتوای XML را حفظ می‌کند، نه چیدمان بایت اصلی یا اعلان رمزگذاری

نقص آخر زیر XFA بود. نویسنده فایل بومی خروجی را در بلوک‌های 32 کیلوبایتی بافر می‌کند و بلوک ناقص نهایی را فقط در destructor اش flush می‌کرد، بعد از اینکه نویسنده سند از قبل موفقیت گزارش کرده بود. یک دیسک-پر یا خطای I/O روی آن بلوک آخر برای فراخواننده نامرئی بود. از v3.125.2 در runtime یعنی V8 و v3.125.3 در runtime معمولی، آن flush نهایی بخشی از نتیجه ذخیره است، و علامت ویرایش XFA فقط بعد از یک موفقیت واقعی پاک می‌شود. در سمت دلفی، مقدار TPdf.SaveAs(const FileName: string; Option: TSaveOption = saNone; PdfVersion: TPdfVersion = pvUnknown): Boolean در فایلی موقت کنار هدف می‌نویسد و فقط وقتی ذخیره True برگرداند آن را سر جایش می‌برد، پس یک ذخیره ناموفق فایل قبلی را سالم می‌گذارد

چرا یک فرم پویای XFA با صفحه‌های کمتر دوباره باز می‌شود؟

یک فرم پویای XFA وقتی با صفحه‌های کمتر باز می‌شود که subform ریشه‌اش restoreState="auto" اعلان نکرده باشد، و آن یک تصمیم نویسندگی فرم است نه نقص PDFium Component. در XFA 3.3، مقدار restoreState روی subform ریشه به‌طور پیش‌فرض manual است. زیر manual، پردازنده XFA فقط state محدودی را از پاکت فرم ذخیره‌شده برمی‌گرداند و بقیه را به اسکریپت‌های نویسنده می‌سپارد. مقدار فیلدهای ذخیره‌شده و شمارنده‌های instance مربوط به subform تکرارشونده همچنان برمی‌گردند، اما ویژگی‌های هندسی که در زمان اجرا ست شده‌اند نه

حالتی که این را لو داد فرمی سه‌صفحه‌ای بود که اسکریپتش یک subform را به مقدار h="450pt" رشد می‌داد. پاکت فرم ذخیره‌شده ارتفاع جدید و مقدارها و شمارنده‌های instance را داشت. اما موقع باز کردن دوباره، layout از ارتفاع‌های template بازسازی می‌شد و فرم روی دو صفحه reflow می‌شد. runtime حق داشت: template هرگز بازسازی خودکار را درخواست نکرده بود. اعلانش روی subform ریشه باز شدن دوباره را فیکس می‌کند:

<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/">
  <subform name="form1" layout="tb" restoreState="auto">
    <pageSet>
      <pageArea name="Page1">
        <contentArea x="0.25in" y="0.25in" w="8in" h="10.5in"/>
        <medium stock="letter"/>
      </pageArea>
    </pageSet>
    <subform name="Details" layout="tb" w="7.5in">
      <!-- فیلدها؛ اسکریپت‌ها ممکن است h را عوض کنند یا در زمان اجرا instance اضافه کنند -->
    </subform>
  </subform>
</template>

اگر مالک template نیستید، در viewer دورش پچ نزنید: فرمی که به حالت manual تکیه دارد انتظار دارد اسکریپت‌های خودش state را بازسازی کنند. دوباره-صفحه‌بندی زنده موقع تایپ کاربر موضوع جداگانه‌ای است، که در نحوه دنبال کردن تعداد صفحه و فیلدهای جابه‌جاشده dynamic XFA توسط PDFium Component پوشش داده شده

یک ذخیره XFA را در دلفی چطور تأیید کنید؟

تنها چک ذخیره XFA قابل-اعتماد، باز کردن فایل ذخیره‌شده در یک instance تازه از TPdf و خواندن برگشتی داده ذخیره‌شده است. مقدار TPdf.GetXfaDatasets پاکت datasets را همان‌طور که در سند ذخیره شده برمی‌گرداند نه مدل داده زنده XFA را، پس صداش قبل از ذخیره مقدارهای قدیمی را نشان می‌دهد. بعد از باز کردن دوباره، دقیقاً همان چیزی را نشان می‌دهد که نوشته شده. یک سند تک-stream پاکت‌هایی با نام جداگانه ندارد: PDFium کل XDP را به‌عنوان یک پاکت با نام خالی گزارش می‌کند، پس GetXfaPacketByName('datasets') و GetXfaDatasets هیچ برنمی‌گردانند، و fallback کل stream را از طریق GetXfaFormPackets می‌خواند

uses
  System.SysUtils, PDFium, FPdfXfa;

function ReadSavedXfaData(const FileName: string): string;
var
  Pdf: TPdf;
  Packets: TXfaPacketList;
  Bytes: TBytes;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;
    Bytes := Pdf.GetXfaDatasets;          // چیدمان آرایه-پاکت
    if Length(Bytes) = 0 then
    begin
      Packets := Pdf.GetXfaFormPackets;   // تک-stream: یک پاکت بی‌نام
      if Length(Packets) = 1 then
      begin
        SetLength(Bytes, Length(Packets[0].Content));
        if Length(Bytes) > 0 then
          Move(Packets[0].Content[0], Bytes[0], Length(Bytes));
      end;
    end;
    Result := TEncoding.UTF8.GetString(Bytes);  // خروجی XDP ذخیره‌شده UTF-8 است
  finally
    Pdf.Free;
  end;
end;

روتین ذخیره بعد ویرایش معوق را commit می‌کند، نتیجه SaveAs را چک می‌کند و مقدار باز-بازشده را مقایسه می‌کند. مقدار TPdf.ClearFormFieldFocus فوکوس فرم را می‌کشد، که همان لحظه‌ای است که PDFium بافر ویرایش فیلد فوکوس‌شده را commit می‌کند. مقدار TPdf.SetFocusedFormFieldText(const Value: WString): Boolean فیلد فوکوس‌شده را به‌صورت برنامه‌نویسی پر می‌کند، اما به فوکوس‌ای تکیه دارد که wrapper آن را از طریق FocusFormField دنبال می‌کند، که annotationهای widget را می‌پیماید. یک صفحه XFA پویا معمولاً هیچ‌کدام را ندارد، پس آنجا متن معمولاً از طریق ورودی صفحه‌کلید در TPdfView می‌رسد، و تابع وقتی هیچ فیلد ردیابی‌شده‌ای فوکوس ندارد False برمی‌گرداند

function XmlText(const S: string): string;
begin
  Result := StringReplace(S, '&', '&amp;', [rfReplaceAll]);
  Result := StringReplace(Result, '<', '&lt;', [rfReplaceAll]);
end;

procedure SaveXfaAndVerify(Pdf: TPdf; const FileName, FieldTag,
  Expected: string);
var
  Saved: string;
begin
  // پر کردن اسکریپتی اختیاری؛ False یعنی هیچ فیلد ردیابی‌شده‌ای فوکوس ندارد
  if (Pdf.FocusedFormFieldIndex >= 0) and
     not Pdf.SetFocusedFormFieldText(Expected) then
    raise EPdfError.Create('Could not write the focused field');

  Pdf.ClearFormFieldFocus;              // بافر ویرایش را commit می‌کند
  if not Pdf.SaveAs(FileName) then      // شامل flush نهایی (v3.125.2 به بعد)
    raise EPdfError.CreateFmt('Saving %s failed', [FileName]);

  Saved := ReadSavedXfaData(FileName);
  if Pos('<' + FieldTag + '>' + XmlText(Expected) + '</' + FieldTag + '>',
    Saved) = 0 then
    raise EPdfError.CreateFmt('%s did not survive the round trip', [FieldTag]);
end;

تست زیر-رشته را یک smoke test بگیرید. یک المان خالی ممکن است به‌شکل <Tag/> سریال شود، ویژگی‌ها می‌توانند روی المان‌های داده ظاهر شوند، و escape کردن فراتر از & و < یک انتخاب serializer است. برای چک‌های production، XML باز-بازشده را با یک parser واقعی XML load کنید و گره متنی المان داده متصل را مقایسه کنید. چک را دو بار پشت سر هم هم اجرا کنید، چون نقص خط جدید فقط در نسل دوم شکل کاملش را نشان داد

مرجع سریع: چک‌لیست وفاداری ذخیره XFA

  • مقدار pdfium.v8.dll از v3.125.2 یا بعدتر را برای فرم‌های XFA deploy کنید، و v3.125.3 یا بعدتر را برای pdfium.dll معمولی، تا فیکس نوشتن-نهایی در هر دو باشد
  • مقدار LibraryName را به یک مسیر کامل اشاره بدهید و EnableV8Engine را True کنید؛ مسیر مفقود به‌جای load کردن کپی دیگری شکست می‌خورد
  • بعد از باز کردن سند مقدارهای TPdf.XFA و TPdf.XfaRuntimeAvailable را تأیید کنید
  • قبل از SaveAs مقدار ClearFormFieldFocus را صدا بزنید تا فیلد فوکوس‌شده commit شود
  • هرگز نتیجه Boolean یعنی SaveAs را نادیده نگیرید؛ نتیجه False فایل قبلی را سر جایش می‌گذارد
  • با باز کردن در یک TPdf جدید و خواندن GetXfaDatasets تأیید کنید، با fallback به GetXfaFormPackets برای XFA تک-stream
  • با مقدارهای خالی و فاصله‌های ابتدایی و متن چند-خطی و & و یک کاراکتر از صفحه مکمل تست کنید، در طول دو نسل ذخیره
  • برای DTDها و XMLDSig و کامنت‌ها داخل پاکت‌های زنده XFA تک-stream انتظار شکست ذخیره صریح را داشته باشید
  • اگر یک فرم پویا هندسه زمان-اجرایش را موقع باز کردن دوباره از دست می‌دهد، قبل از مشکوک شدن به کتابخانه subform ریشه را برای restoreState="auto" چک کنید

برای ساختار callback ای که runtime یعنی XFA از برنامه میزبان انتظار دارد، نسخه 2 مربوط به FPDF_FORMFILLINFO و ABI یعنی XFA در دلفی را ببینید. ‏runtime یعنی V8 و wrapper مربوط به Delphi و C++Builder و کنترل viewer همگی بخشی از PDFium Component برای Delphi و C++Builder هستند، که هر دو runtime یعنی Windows را برای Win32 و Win64 شامل می‌شود