บทความเทคนิค

Dynamic XFA ใน PDFium Component: จำนวนหน้าคือ delta

เมื่อฟอร์ม dynamic XFA ใน viewer Delphi เพิ่มหรือลดหน้า PDFium Component รายงานยอดรวมใหม่ผ่าน TPdf.PageCount กับ TPdf.OnXfaPageCountChanged ตั้งแต่ v3.126.1 เพราะ page event ระดับ native แบก delta แบบเพิ่ม/ลดมาให้ ไม่ใช่ยอดรวม ไลบรารี Windows V8 ใน v3.126.1 ยังลากพื้นที่รับ input ตาม field ที่ย้ายที่ไปด้วย และ v3.126.2 โหลด page handle ที่เก่าหมดอายุใหม่หลัง layout callback คืนค่า รายงานบั๊กที่เริ่มเรื่องนี้คือฟอร์มเบิกค่าใช้จ่าย กด Add Row สองครั้ง ฟอร์มโตเป็นสองหน้า และตัวบอกหน้ายังโชว์อวดว่า 1 จาก 1 พิมพ์ลง field ที่ย้ายไปหน้า 2 ตัวอักษรก็ไปตกมืด ๆ ที่ไหนสักแห่ง ทั้งหมดนี้ไม่โผล่กับฟอร์มตัวอย่างความยาวคงที่ที่ทุกคนเทสต์เป็นอันดับแรก และเหตุผลเบื้องหลังก็คุ้มที่จะรู้ ถ้าคุณ embed ตัวดูฟอร์มไว้ในแอป

เกิดอะไรขึ้นเมื่อฟอร์ม dynamic XFA จัดหน้าใหม่

ฟอร์ม dynamic XFA ไม่มีรายการหน้าคงที่ จำนวนหน้าของมันจึงเป็น output ของ layout ที่เปลี่ยนได้ทุกครั้งที่ผู้ใช้แก้ข้อมูล XFA 3.3 บรรยายฟอร์มเป็นต้นไม้ของ subform subform แบบทำซ้ำถูกควบคุมด้วย instanceManager สคริปต์อย่าง _Row.addInstance() โคลนแถวเพิ่มมาหนึ่งแถว ตัวประมวลผล layout จากนั้นไหลเนื้อหากลับเข้าพื้นที่หน้าใหม่ ซึ่งอาจเพิ่มหน้า ลดหน้า หรือดัน field เดิมข้ามไปอีกหน้า ISO 32000-1 §12.7.8 นิยามแค่วิธีที่แพ็กเกต XFA ขี่อยู่ใน PDF ทุกอย่างที่เกิดต่อจากนั้นเป็นงานของเอนจิน XFA ซึ่งใน PDFium Component คือ layout ของ XFA ของ PDFium เองที่รันอยู่ใน process ของ host viewer Delphi จึงต้องรับมือกับเอกสารที่จำนวนหน้า ขนาดหน้า กับตำแหน่ง widget ล้วนเป็น state ที่มีชีวิต สามเรื่องนี้พังเมื่อ host สมมติว่าคงที่:

  • จำนวนหน้าที่ host แคชไว้ใช้เดินทาง ช่วง scroll กับ spinner เลือกหน้าเก่าหมดอายุ หรือแย่กว่านั้นคือถูกอัปเดตด้วยตัวเลขที่ผิด
  • field ที่ย้ายบ้านโชว์กรอบในตำแหน่งใหม่ ขณะที่ editor กับพื้นที่รับเมาส์ยังยืนอยู่ที่พิกัดเก่า
  • viewer ยังถือ page handle ที่ layout เปลี่ยนไปแล้ว คลิกกับการวาดจึงไปลงหน้าที่ไม่มีอยู่ในฟอร์มนั้นอีกแล้ว

การคงการแก้แถวไว้ข้ามการเซฟกับเปิดใหม่เป็นอีกปัญหาแยกที่มีกฎของตัวเอง บทความนี้จับแต่สิ่งที่เกิดตอน runtime ข้างใน viewer เท่านั้น

dynamic XFA ต้องใช้ PDFium runtime แบบไหน

dynamic XFA ใน PDFium Component ต้องการบิลด์ V8/XFA ของไลบรารี native ซึ่งเลือกด้วยตัวแปร global EnableV8Engine ในยูนิต PDFium ก่อนเอกสารแรกถูกโหลด process จะผูกใจกับ DLL ตัวเดียวตั้งแต่ครั้งแรกที่ TPdf ใด ๆ โหลดไลบรารี และบิลด์ PDFium ธรรมดาไม่มีทางรันเอนจิน XFA ได้เลย เมื่อเปิดเอกสาร TPdf จะแอบแอะไฟล์หา marker ของ XFA แล้วสลับไปบิลด์ V8 ให้อัตโนมัติ แต่เฉพาะเมื่อยังไม่มีไลบรารีธรรมดาถูกโหลดใน process นั้น ถ้าการผูกใจไปผิดทางไปแล้ว TPdf.OnXfaRuntimeMissing จะยิงหนึ่งครั้งให้ host บอกผู้ใช้ว่าต้องรีสตาร์ท การตั้ง flag นี้ชัด ๆ ตอนสตาร์ทตัดเรื่องเดาออกไปทั้งหมด โครงสร้าง callback FPDF_FORMFILLINFO ที่แบก event ของ XFA ก็ต้องตรงกับ DLL พื้นหลังอยู่ในFPDF_FORMFILLINFO เวอร์ชัน 2 กับ ABI ของ callback XFA และการตรวจจับฟอร์ม XFA กับการอ่านแพ็กเกตของมัน เล่าวิธีแยกชนิดฟอร์มก่อนเปิด viewer

uses
  PDFium;

procedure TClaimForm.FormCreate(Sender: TObject);
begin
  // ตัดสินใจก่อน TPdf ตัวแรกโหลดไลบรารี native:
  // process สลับจาก pdfium.dll ไป pdfium.v8.dll ภายหลังไม่ได้
  EnableV8Engine := True;

  FPdf := TPdf.Create(nil);
  FPdf.OnXfaRuntimeMissing := PdfXfaRuntimeMissing;
  FPdf.OnXfaPageCountChanged := PdfXfaPageCountChanged;
  FPdf.FileName := 'C:\Forms\expense-claim.pdf';
  FPdf.Active := True;

  PdfView1.Pdf := FPdf;
  PdfView1.OnPageChange := PdfViewPageChange;
  PdfView1.Active := True;

  UpdatePageRange(FPdf.PageCount);
end;

procedure TClaimForm.PdfXfaRuntimeMissing(Sender: TObject);
begin
  StatusBar1.SimpleText :=
    'This XFA form needs the V8 runtime; restart the application to enable it';
end;

ทำไม PageCount ถึงรายงาน 1 ทั้งที่ฟอร์มมีสองหน้า

ก่อน v3.126.1 PDFium Component เก็บอาร์กิวเมนต์ page_count ของ page event ระดับ native ไว้เป็นยอดรวมของเอกสาร ทั้งที่อาร์กิวเมนต์นั้นจริง ๆ คือค่าสัมบูรณ์ของผลต่างระหว่างจำนวนหน้าใหม่กับเก่า PDFium ยิง FFI_PageEvent หลัง layout pass จบ ด้วยชนิด event เป็นหน้าถูกเพิ่มหรือหน้าถูกลบ ข้างในมันอัปเดตจำนวนหน้าที่เก็บไว้ก่อน แล้วจึงส่ง abs(new - old) ออกมา บน layout แรกจำนวนเก่าเป็นศูนย์ delta จึงเท่ากับยอดรวม และตัวอย่าง static สามหน้าก็รายงานสามหน้าตามคาด นั่นเป๊ะคือเหตุที่ฟอร์มทดสอบความยาวคงที่ไม่เคยเปิดเผยบั๊กนี้ ครั้งแรกที่ฟอร์ม dynamic โตจากหน้าเดียวเป็นสองหน้า delta คือ 1 และ wrapper ตั้งทั้ง TPdf.PageCount กับพารามิเตอร์ NewCount ของ OnXfaPageCountChanged เป็น 1 ลบแถวออกจากฟอร์มสามหน้าก็ผลิตความไม่สมเหตุผลแบบเดียวกันไปในทิศตรงข้าม

การเอา delta ไปบวกสะสมกับค่าเดิมก็ไม่ใช่การแก้ที่ปลอดภัย ลำดับของ callback ตอน initialize กับตอน layout ทำให้ wrapper เชื่อจำนวนที่ตัวเองเก็บไว้ก่อนหน้าเป็น baseline ไม่ได้เสมอ ผลรวมวิ่งจึงเลื่อนคลาดได้ ตั้งแต่ v3.126.1 callback เมินอาร์กิวเมนต์ในฐานะจำนวนหน้า แล้วเรียก FPDF_GetPageCount บนเอกสารแทน ซึ่งอ่านยอดรวมจาก layout ที่เพิ่งจบไป มันจึงเคลียร์ page scene ที่แคชไว้ทิ้ง เก็บยอดนั้นเป็นค่า override จำนวนหน้า XFA ที่ซ่อนอยู่หลัง TPdf.PageCount และยิง OnXfaPageCountChanged เป็นลำดับสุดท้าย เมื่อ handler ของคุณรันถึงตัว NewCount กับ FPdf.PageCount จึงพูดภาษาเดียวกัน

แผนภาพ dynamic XFA ของ PDFium Component ที่การเพิ่มแถวทำให้ฟอร์มหน้าเดียวจัดใหม่เป็นสองหน้า และ FFI_PageEvent ส่ง abs(ใหม่ ลบ เก่า) มาเป็น delta wrapper ตัวเก่าจึงรายงาน TPdf.PageCount เป็น 1 ขณะที่ v3.126.1 อ่าน FPDF_GetPageCount แล้วรายงานยอดรวมที่ถูกต้อง
page event ระดับ native รายงาน delta แบบเพิ่มหรือลด ไม่ใช่ยอดรวม v3.126.1 จึงเมินอาร์กิวเมนต์ทิ้ง อ่าน layout ที่จบแล้วก่อน แล้วค่อยยิง OnXfaPageCountChanged
procedure TClaimForm.PdfXfaPageCountChanged(Sender: TObject; NewCount: Integer);
begin
  // v3.126.1 ขึ้นไป: NewCount คือยอดรวมของ layout ที่จบแล้ว ไม่ใช่ delta เด็ดขาด
  // ตัวนี้รันข้างใน layout callback ของ PDFium: อัปเดตแค่ UI state ฝั่ง host
  // อย่าปิดเอกสารหรือโหลดหน้าใหม่จากตรงนี้
  UpdatePageRange(NewCount);
end;

procedure TClaimForm.PdfViewPageChange(Sender: TObject);
begin
  // ยิงหลังโหลดหน้าใหม่ทุกครั้ง รวมการรีเฟรช XFA แบบเลื่อนไปก่อน
  PageSpin.Value := PdfView1.PageNumber;
end;

procedure TClaimForm.UpdatePageRange(Count: Integer);
begin
  PageSpin.MinValue := 1;
  PageSpin.MaxValue := Count;
  PageLabel.Caption := Format('of %d', [Count]);
end;

event นี้ยิงเฉพาะฟอร์ม Full XFA ที่ layout เปลี่ยนตอน runtime เอกสาร static XFA กับ AcroForm ไม่เคยยิงมัน viewer ที่รับทั้งสองแบบจึงปล่อย handler เดิมผูกไว้ได้ ปล่อยว่างไม่ผูกก็ปลอดภัย override ที่อยู่หลัง TPdf.PageCount ถูกใช้ไม่ว่ากรณีใด event มีไว้เพื่อให้ host รีเฟรชสิ่งที่ตัวเองแคชไว้เท่านั้น

ทำไมกล่อง input ถึงค้างอยู่หน้าเก่าเมื่อ field ย้ายที่

กรอบขยับแต่ editor ไม่ขยับ เพราะตัวแจ้งเตือน XFA ระดับ native ไปเทียบ rectangle กับตัวเอง เมื่อ layout เปลี่ยนเรขาคณิตของ widget ที่โหลดไว้แล้ว PDFium ควรสังเกตเห็น rectangle ใหม่และเรียก PerformLayout บน widget ซึ่งจะจัดตำแหน่ง text editor กับพื้นที่รับเมาส์ใหม่ แต่การเช็กเทียบ GetWidgetRect() กับ RecacheWidgetRect() ทั้งสองฟังก์ชันคืน const reference ไปยัง member ตัวเดียวกัน และการ recache เขียนทับ member นั้นแหลก การเทียบจึงเห็นค่าสองค่าที่เหมือนกันเสมอ และ widget ที่โหลดไว้ก็ข้ามการจัด layout ใหม่ไปเฉย ๆ

อาการปรากฏเมื่อเทสต์เปลี่ยนความสูงของ subform ให้ field เดิมข้ามไปตกหน้าถัดไป บนสถาปัตยกรรม V8 ทั้งสองแบบ กรอบของ field ถูกวาดที่ตำแหน่งใหม่ ขณะที่ข้อความที่พิมพ์กับพื้นที่รับเมาส์ยังติดอยู่ที่พิกัด Y เดิม สั่ง layout ใหม่ตรง ๆ ไม่แก้ โหลดหน้าใหม่ก็ไม่แก้ เพราะ widget ยังเชื่อว่าเรขาคณิตของมันทันสมัยอยู่ ไลบรารี Windows V8 ที่มาพร้อม v3.126.1 ก๊อปปี้ rectangle เก่าด้วยค่าก่อน recache แล้วเอาสำเนานั้นไปเทียบ widget ที่ถูกย้ายจึงจัด layout ใหม่ และค่าที่พิมพ์ปรากฏพอดีตรงที่กรอบอยู่ นี่เป็นการแก้ระดับ native มันเดินทางมากับตัว DLL อัปเดตยูนิต Pascal ไปแต่เก็บ pdfium.v8.dll รุ่นเก่าไว้ก็ยังมีพื้นที่รับเมาส์หลุดตำแหน่งเหมือนเดิม เทสต์ regression ที่ขับเคลื่อนการแก้นี้แก้แถวที่รอดมาให้เป็นค่าที่ไม่ใช่ default ก่อน แล้วจึงเรียกให้ค่านั้นต้องอยู่ที่ตำแหน่งใหม่ของ field เพราะแถวที่ถูกสร้างใหม่ด้วยค่า default จะดูผ่านไปได้โดยไม่พิสูจน์อะไรเลย

แผนภาพการจัด layout ใหม่ของ widget ใน PDFium Component เทียบการเทียบกับตัวเองแบบเก่าที่ GetWidgetRect กับ RecacheWidgetRect คืน member เดียวกัน widget ที่ถูกย้ายจึงข้าม PerformLayout กับการเช็กก๊อปปี้ด้วยค่าของ Windows V8 ใน v3.126.1 ที่จัดตำแหน่ง editor กับพื้นที่รับเมาส์ไปทับกรอบที่วาดใหม่
การเทียบ rectangle กับตัวเองไม่มีทางล้มเหลว กรอบจึงขยับไปคนเดียว ข้อความที่พิมพ์กับการคลิกยังค้างหลัง จนกว่าการเช็กจะก๊อปปี้สำเนาด้วยค่าไว้ก่อน

TPdfView โหลดหน้าใหม่โดยไม่ตวัด handle ออกจากใต้ PDFium ได้อย่างไร

ตั้งแต่ v3.126.2 TPdfView เลื่อนการโหลดหน้าใหม่ที่ตามหลังการเปลี่ยน layout ของ XFA ออกไปจนกว่า call stack ระดับ native จะคลี่ออกจนหมด page event มักยิงขณะที่ PDFium ยังกำลังประมวลผล input: ผู้ใช้คลิกปุ่ม Add Row การคลิกรันสคริปต์ สคริปต์เปลี่ยนจำนวน instance และ layout จบภายใน call ระดับ native ชุดเดียวกัน การปิดแล้วเปิด page handle ตรงนั้นจะ free ออบเจกต์ที่ผู้เรียกยังใช้งานอยู่ ก่อน v3.126.2 viewer แค่ invalidate ตัวเอง page handle ที่แสดงอยู่จึงอาจยังชี้ไปที่ state ก่อน layout และถ้าผู้ใช้ยืนอยู่หน้าสุดท้ายตอนที่หน้าหายไป เลขหน้าที่เลือกก็ออกนอกช่วงไปเลย

การรีเฟรชแบบเลื่อนทำงานเป็นขั้นเล็ก ๆ ไม่กี่ขั้น ซึ่งอธิบายพฤติกรรมที่ host เห็นได้:

  1. callback ของ page event ติดป้ายว่า view มีการรีเฟรช layout ของ XFA ค้างอยู่ แล้วโพสต์ private window message หนึ่งคำสั่ง event ที่ยิงซ้ำก่อนข้อความจะถึงจะถูกหลอมรวมเป็นการรีเฟรชครั้งเดียว
  2. view ที่ยังไม่มี window handle คง flag ค้างไว้แล้วโพสต์ข้อความจาก CreateWnd ส่วนการเปลี่ยนเอกสาร การปิดการทำงานของ view หรือการทำลายมันจะเคลียร์ flag ทิ้ง
  3. เมื่อข้อความมาถึง view เคลียร์การเลือกข้อความ ไฮไลต์การค้นหา กับ index ของ field ที่โฟกัส เพราะทั้งสามอย่างชี้ไปที่ layout เก่าหมด
  4. หน้าที่เลือกถูก clamp เข้า PageCount ใหม่ เลขหน้าที่เปลี่ยนไปเดินผ่านการสลับหน้าตามปกติ ไม่งั้นหน้าปัจจุบันถูกโหลดใหม่ และโหมด fit ถูกใช้อีกครั้ง
  5. ถ้า layout ทิ้งหน้าไว้ไม่เหลือเลย view จะเอา page handle เก่าออกแทนที่จะวาดหน้าที่ไม่มีอยู่จริง
แผนภาพการรีเฟรช XFA แบบเลื่อนของ TPdfView ใน PDFium Component ที่ page event ข้างใน call stack ของ layout ระดับ native แค่ติดป้ายว่ามีการรีเฟรชค้างและโพสต์ window message ซึ่งภายหลังเคลียร์ state การเลือกที่เก่าหมดอายุ clamp หน้าเข้า PageCount ใหม่แล้วโหลดหรือเอา page handle ออก
การโหลดใหม่รอจน call stack ระดับ native คลี่ออก: ข้อความที่โพสต์หลอม event ซ้ำให้เหลือรอบเดียว แล้ว view clamp หน้า โหลดใหม่ และยิง OnPageChange

ข้อจำกัดเดียวกันใช้กับโค้ดของคุณเอง OnXfaPageCountChanged รันข้างใน layout callback ระดับ native นั้น จึงควรต่อยอดมันเหมือนการแจ้งเตือน: อัปเดต label, ช่วงของ spinner กับสถานะ toolbar ที่นั่น แล้วจัดคิวงานหนัก ๆ อย่างปิดเอกสารหรือเปิดเอกสารอื่น ด้วยการโพสต์ข้อความ ให้มันรันหลัง callback คืนค่า TPdfView.OnPageChange จะบอกคุณว่า view โหลดหน้าใหม่จริงแล้วเมื่อไร และการอ่าน PdfView1.PageNumber ตอนนั้นให้ค่าที่ถูก clamp แล้ว การเดินแท็บระหว่าง field กับการเช็ก FormType ที่ form viewer รันตอนเปิดเล่าไว้ในการเดินสนามฟอร์ม PDF ด้วย PDFium Component

ทำไมคลิก field ของ Full XFA ถึงขึ้น "Cannot open text page"

หน้า Full XFA ไม่มี text page ของ PDF และก่อน v3.126.2 การเลือกข้อความกับการตรวจจับลิงก์ค่าเริ่มต้นของ viewer ก็ยังพยายามโหลดมันขึ้นมา เมื่อ TPdfView.AllowUserTextSelection อยู่ที่ค่าเริ่มต้น True การชี้เมาส์จะถาม text layer หาอักขระใต้เคอร์เซอร์ และการปล่อยคลิกจะวิ่ง probe URL อัตโนมัติบนข้อความหน้า บนหน้า Full XFA text page เปิดไม่ได้ การคลิกธรรมดาเข้า field หนึ่งครั้งจึงอาจจบด้วย exception Cannot open text page ตั้งแต่ v3.126.2 ทั้งสองเส้นทางภายในคืนผลว่างเมื่อ TPdf.FormType เป็น ftXfaFull และ runtime ของ XFA พร้อมใช้งาน ค่าเริ่มต้นจึงใช้งานได้และการกรอก field ยังเปิดอยู่

การปิด AllowUserTextSelection กับเอกสาร Full XFA ยังเป็นทางเลือก UI ที่สมเหตุผลอยู่ เพราะไม่มีข้อความหน้าให้เลือก และท่าลากไม่ควรเปิดโหมดเลือกขึ้นมา แต่มันไม่ใช่ตัวแทนของการอัปเดต บนเวอร์ชันก่อนหน้า probe URL ตอนคลิกไม่ได้พึ่ง property ตัวนี้เลย viewer ที่ปิดการเลือกข้อความไว้ก็โดน exception เดียวกันได้

procedure TClaimForm.ConfigureViewerForForm;
begin
  // FormType อ่านจากเอกสารที่เปิดอยู่ เรียกหลัง FPdf.Active := True จึงจะถูก
  if FPdf.XFA and (FPdf.FormType = ftXfaFull) and FPdf.XfaRuntimeAvailable then
  begin
    // ไม่มี text layer ของ PDF บนหน้า Full XFA field ยังแก้ไขได้
    PdfView1.AllowUserTextSelection := False;
    StatusBar1.SimpleText := Format('Dynamic XFA form, %d page(s)',
      [FPdf.PageCount]);
  end
  else
    PdfView1.AllowUserTextSelection := True;
end;

การพิมพ์ได้การซ่อมของตัวเองใน v3.126.2 text editor ระดับ native ของ XFA ไม่แทนที่ selection เมื่อรับอักขระ FORM_OnChar แทรกที่ caret และ Backspace ลบทีละหนึ่งตัว การเลือกค่าแล้วพิมพ์ทับจึงได้ข้อความเก่ากับใหม่นั่งเรียงกัน PDFium Component ตอนนี้จำไว้ว่าการคลิกตกลงบน text field ของ XFA แล้วส่งอักขระที่พิมพ์, Backspace กับ Delete ผ่าน FORM_ReplaceSelection ทุกครั้งที่มี selection อยู่และเอกสารให้สิทธิ์กรอกฟอร์มหรือแก้ไข ว่า field XFA แบบ read-only เปลี่ยนได้หรือไม่ยังเป็นการตัดสินของ editor ระดับ native field ที่ติดป้าย read-only ในฟอร์มจึงคงค่าเดิมแม้อยู่ในเอกสารที่อนุญาตให้กรอก การตั้ง TPdfView.AllowFormEvents เป็น False ก็หยุดเส้นทางคีย์บอร์ดนี้ด้วย viewer แบบอ่านอย่างเดียวจึงยังอ่านอย่างเดียว

สรุป: dynamic XFA ใน viewer Delphi

อาการสาเหตุแก้แล้วใน
จำนวนหน้าโชว์ 1 หลังฟอร์มโตเป็นสองหน้าpage event ระดับ native ส่ง delta แบบเพิ่ม/ลด ไม่ใช่ยอดรวมv3.126.1 (wrapper)
กรอบ field ขยับ ข้อความที่พิมพ์กับพื้นที่รับเมาส์ค้างหลังwidget ที่โหลดไว้ข้ามการจัด layout ใหม่หลังการเทียบกับตัวเองv3.126.1 (ไลบรารี Windows V8)
viewer วาดหรือส่ง input ไป state หน้าก่อน layoutpage handle ไม่ถูกโหลดใหม่หลังจัดหน้าใหม่v3.126.2 (รีเฟรชแบบเลื่อน)
คลิกเข้า field ขึ้น Cannot open text pageการเลือกข้อความกับ probe URL บนหน้าที่ไม่มี text layerv3.126.2
พิมพ์ทับค่าที่เลือกไว้กลายเป็นการต่อท้ายแทนการแทนที่editor XFA ระดับ native แทรกที่ caretv3.126.2
  • ตั้ง EnableV8Engine เป็น True ก่อนเอกสารใดถูกโหลด แล้วจัดการ OnXfaRuntimeMissing ไว้สำหรับกรณีที่ไลบรารีธรรมดาถูกโหลดก่อน
  • อ่านยอดรวมจาก TPdf.PageCount หรือพารามิเตอร์ NewCount ของ OnXfaPageCountChanged อย่าบวกลบจำนวนหน้าเองเด็ดขาด
  • ให้ handler ของ OnXfaPageCountChanged เบาเอาไว้ เพราะมันรันข้างใน layout callback ระดับ native
  • ซิงก์ตัวบอกหน้าปัจจุบันใน TPdfView.OnPageChange ซึ่งยิงหลังการโหลดใหม่แบบเลื่อน clamp เลขหน้าให้แล้ว
  • deploy DLL Windows V8 รุ่น v3.126.1 ขึ้นไปไปพร้อมกับยูนิต การแก้จัด layout ของ widget อยู่ในโค้ด native
  • เทสต์กับฟอร์มที่เปลี่ยนจำนวนหน้าจริงและดึง field ที่แก้ไขข้ามเส้นแบ่งหน้า ฟอร์มตัวอย่างความยาวคงที่ซ่อนทุกบั๊กในลิสต์นี้ได้หมด

dynamic XFA เปลี่ยนจำนวนหน้ากับเรขาคณิตของ field ให้เป็นค่ามีชีวิต และ viewer จะยังถูกต้องก็ต่อเมื่อมันหยิบค่าจาก layout ที่จบแล้วและโหลดหน้าใหม่ในช่วงเวลาที่ปลอดภัย PDFium Component จัดการทั้งสองเรื่องข้างใน TPdf กับ TPdfView host จึงเหลือหน้าที่แค่ฟัง รายละเอียดกับดาวน์โหลดอยู่ที่หน้าผลิตภัณฑ์ PDFium Component for Delphi