ฟอร์มสองแบบอาจมีฟิลด์เหมือนกันแต่พฤติกรรมต่างกันโดยสิ้นเชิง AcroForm เก็บฟิลด์ของมันไว้เป็น PDF object ธรรมดาที่วางอยู่บนเนื้อหาหน้าจริง ดังนั้นผู้อ่านที่ปฏิบัติตามมาตรฐานตัวไหนก็วาดมันได้ ฟอร์ม XFA แบบไดนามิกแทบไม่เก็บอะไรไว้เป็น PDF เลย: ฟิลด์ เลย์เอาต์ แม้กระทั่งเรขาคณิตของหน้าล้วนอยู่ใน XML package และหน้าที่มองเห็นได้ถูกสร้างขึ้นตอนเปิดไฟล์โดย layout engine ที่มีเพียง Adobe เท่านั้นที่เคยส่งมอบให้ใช้งานกันอย่างแพร่หลาย ป้อนไฟล์นั้นให้ web viewer ตัว renderer สำหรับ archive หรือตัวดึงข้อความ แล้วคุณจะไม่ได้ฟอร์มนั้นเลย คุณจะได้หน้าสีเทาว่างเปล่าหน้าเดียวที่เขียนว่า "Please wait... If this message is not eventually replaced by the proper contents of the document, your PDF viewer may not be able to display this type of document" ใครก็ตามที่เคยรับเอกสารราชการหรือประกันภัยเข้าระบบจะจำหน้านี้ได้ทันทีที่เห็น
placeholder นี้ไม่ใช่ความเสียหาย มันคือสิ่งที่ฟอร์แมตกำหนดไว้พอดีว่าควรเกิดขึ้นเมื่อไม่มี XFA processor อยู่ และ ณ ปี 2026 นั่นก็คือ viewer เกือบทุกตัวนอกเหนือจาก Acrobat เดสก์ท็อป ดังนั้นวิธีที่ใช้งานได้จริงคือแปลงฟอร์มไดนามิกให้เป็น AcroForm ธรรมดาก่อนที่มันจะไปถึงขั้นตอนปลายทางใด ๆ HotPDF ซึ่งเป็นไลบรารี PDF ของ losLab สำหรับ Delphi และ C++Builder ทำการแปลงนี้ในโค้ด โดยสร้างฟอร์ม XML ขึ้นมาใหม่เป็นฟิลด์แบบ native บนหน้า native
ทำไมสองโมเดลนี้ถึงอยู่ร่วมกันไม่ได้
AcroForm ถูกนิยามไว้ใน ISO 32000-1 §12.7 แต่ละฟิลด์เป็น PDF object ที่มี widget annotation และ appearance stream หน้าเป็นเนื้อหา PDF แท้ ๆ และข้อมูลก็ขี่อยู่บนนั้น XFA กลับเรื่องนี้ทั้งหมด: ฟอร์มเป็นเอกสาร XML เป็น XDP package ที่เก็บไว้ในรายการ /XFA ของ dictionary ของ AcroForm และหน้า PDF ของฟอร์มไดนามิกจะมีแค่ placeholder "Please wait" อยู่และไม่มีอะไรอย่างอื่นเลย เพราะเนื้อหาจริงไม่เคยถูก serialize เป็น PDF เลย ผู้อ่านจะประมวลผลไฟล์เป็นโมเดลใดโมเดลหนึ่งเท่านั้น ถ้าเพิกเฉยรายการ /XFA คุณจะเห็นเปลือกที่ว่างเปล่า ถ้าเคารพมันโดยไม่มี XFA engine คุณจะเห็นคำเตือน ISO 32000-2 ยุติข้อถกเถียงนี้ด้วยการตัด XFA ออกจาก PDF 2.0 ไปเลย ซึ่งเป็นเหตุผลหลักที่ "แปลงตอนที่ยังทำได้" เปลี่ยนจากกรณีขอบ ๆ ไปเป็นนโยบายการรับเข้าตามปกติ
ก่อนแปลงอะไรก็ตาม ให้จัดหมวดหมู่มันก่อน เพราะไม่ใช่ทุกไฟล์ XFA จะแสดง placeholder ฟอร์ม XFA แบบ static จะส่งมาพร้อมหน้า PDF ที่ render ไว้ล่วงหน้าเคียงข้างกับ XML ดังนั้นมันแสดงผลได้ทุกที่และจะมีปัญหาก็ต่อเมื่อกรอกข้อมูลเท่านั้น ฟอร์มไดนามิกส่งมาพร้อม placeholder เพียงอย่างเดียวและใช้งานไม่ได้จนกว่าจะแปลง สิ่งที่ควรเชื่อคือตัวเอกสารเอง ไม่ใช่นามสกุลไฟล์หรือผู้ส่ง ไฟล์ที่ render เนื้อหาจริงใน viewer ที่ไม่ใช่ของ Adobe แต่ยังคงมีรายการ /XFA อยู่ คือแบบ static หรือ hybrid ไฟล์ที่แสดงหน้าคำเตือนคือแบบไดนามิก ให้บันทึกไว้ว่าไฟล์ที่รับเข้ามาแต่ละไฟล์ตกอยู่ในกลุ่มไหน ทั้งสองแบบจะพังในแบบที่ต่างกันภายหลัง และ ticket เรื่องฟอร์ม archive ที่ว่างเปล่าจะปิดได้ภายในไม่กี่วินาที เมื่อ intake log บันทึกไว้แล้วว่า "dynamic XFA, converted, 47 fields mapped, 2 warnings"
การแปลงเอกสาร XFA ที่โหลดไว้ให้เป็นฟิลด์แบบ native
การแปลงนี้ทำงานกับเอกสารที่อยู่ในหน่วยความจำแล้ว FlattenLoadedXFA จะ parse XFA template และ data packet ของมัน จัดเลย์เอาต์ฟอร์ม และสร้างมันขึ้นมาใหม่เป็นฟิลด์ AcroForm บนหน้า PDF จริง:
var
Pdf: THotPDF;
MappedCount, I: Integer;
Warnings: TStrings;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('dynamic_xfa.pdf');
MappedCount := Pdf.FlattenLoadedXFA(True); // True = ฟิลด์ยังคงแก้ไขได้
Warnings := Pdf.XFAFlattenWarnings;
for I := 0 to Warnings.Count - 1 do
Log('XFA flatten warning: ' + Warnings[I]); // element ที่แม็ปไม่ได้
Pdf.SaveLoadedDocument('native_acroform.pdf');
Log(Format('Mapped %d fields', [MappedCount]));
finally
Pdf.Free;
end;
end;
ค่าที่ return กลับมาและรายการคำเตือนคือ output ไม่ใช่เสียงรบกวนสำหรับดีบัก ดังนั้นเก็บทั้งคู่ไว้ การแปลงจะสูญเสียข้อมูลไปโดยธรรมชาติ: การเขียนสคริปต์ของ XFA ฟิลด์ที่คำนวณได้ และพฤติกรรม subform แบบไดนามิกไม่มีสิ่งที่เทียบเท่าใน AcroForm และ XFAFlattenWarnings ระบุชื่อ element ของ template ทุกตัวที่แม็ปไม่ได้ ถ้า archive ไฟล์ที่แปลงแล้วโดยไม่มีรายการคำเตือนของมัน สักวันหนึ่งคุณจะต้องจ้องมองกล่องยอดรวมที่ว่างเปล่าในสำเนา archive โดยไม่มีบันทึกว่าทำไม flag Editable ควบคุมว่าฟิลด์ใหม่จะยังกรอกได้อยู่หรือไม่ ส่ง True เมื่อผู้คนยังคงทำงานกับฟอร์มต่อไปภายหลัง และล็อกค่าไว้เมื่อเป้าหมายคือบันทึกที่หยุดนิ่งแล้ว
การตรวจสอบการแปลงมีทั้งส่วนที่เป็นภาพและส่วนที่เป็นโครงสร้าง และคุณต้องการทั้งสองส่วนนี้ ส่วนที่เป็นโครงสร้างทำได้ง่าย: ยืนยันว่าจำนวนฟิลด์ตรงกับ MappedCount ส่วนที่เป็นภาพคือส่วนที่จับความเสียหายจริง ๆ ได้ เปิดฟอร์มต้นฉบับใน Acrobat เดสก์ท็อป ซึ่งยังคงเป็น viewer ตัวเดียวที่รัน XFA engine ไว้เคียงข้างกับไฟล์ที่แปลงแล้วในโปรแกรมอ่านทั่วไป แล้วเทียบค่าและเลย์เอาต์บนตัวอย่างที่กรอกแล้วอย่างน้อยหนึ่งชุดต่อหนึ่ง template วันที่ที่ XFA engine แสดงเป็น 2026-06-11 อาจตกไปอยู่ในสำเนา AcroForm เป็นค่าดิบที่ไม่ได้จัดรูปแบบ และมีแค่สายตาของคุณเท่านั้นที่จะจับมันได้
เมื่ออินพุตเป็น XDP package
ไม่ใช่ทุกงานที่เริ่มจาก PDF ที่มีข้อมูลอยู่แล้ว บางครั้งคุณได้รับ XDP package มาเดี่ยว ๆ ที่ export มาจากเครื่องมือออกแบบฟอร์มหรือส่งมาจากระบบพาร์ตเนอร์ ApplyXFAAsAcroForm ข้ามขั้นตอนการโหลดไปและใช้ package นั้นกับเอกสารปัจจุบันโดยตรง:
XDPBytes := TFile.ReadAllBytes('benefit-claim.xdp');
MappedCount := Pdf.ApplyXFAAsAcroForm(XDPBytes, True);
ฟังก์ชันเรียกกลุ่มเดียวกันนี้ยังทำงานในทิศทางตรงข้ามได้ด้วย สำหรับกรณีที่พบไม่บ่อยที่คุณต้องสร้าง XFA ออกมาแทนที่จะบริโภคมัน AddXFAPacket แนบ packet ที่ตั้งชื่อไว้แต่ละตัว เช่น 'xdp' หรือ 'config' SetXFADocument ติดตั้ง payload แบบ single-stream ที่สมบูรณ์ในการเรียกครั้งเดียว ClearXFAPackets ล้างการลงทะเบียนเพื่อให้คุณเริ่มใหม่ได้ และ AddXFASignaturePacket ฝังเอกสาร XAdES สำหรับ workflow ที่เซ็นข้อมูลฟอร์ม XML โดยตรง การสร้าง XFA ขึ้นมาในปี 2026 เป็นความต้องการเฉพาะกลุ่มเล็ก ๆ ที่แทบจะถูกบังคับโดยผู้บริโภคระบบเก่าเพียงรายเดียวที่ปฏิเสธอย่างอื่นทั้งหมด แต่เมื่อสัญญาระบุไว้ ฟังก์ชันเรียกเหล่านี้จะทำให้มันเหลือแค่ทางเลือกการตั้งค่า แทนที่จะต้องใช้เครื่องมือแยกต่างหาก
ความหมายอีกอย่างของคำว่า "flatten"
คำว่า "flatten" ทำให้บทสนทนาจำนวนมากสับสน เพราะมันตั้งชื่อให้กับการทำงานอีกแบบหนึ่งไปเลยโดยสิ้นเชิง: การเผา appearance ของฟิลด์ AcroForm ลงไปใน content stream ของหน้าจนไม่เหลือ object แบบโต้ตอบได้อยู่เลย HotPDF ยังไม่มี API สำหรับสิ่งนั้นในตอนนี้ และคุณควรรู้เรื่องนี้ตั้งแต่ตอนนี้ ดีกว่าไปรู้ตอนโปรเจกต์ทำไปครึ่งทางแล้ว สิ่งที่ไลบรารีมอบให้คุณแทนคือการล็อกในระดับฟิลด์ตอนที่ฟิลด์ถูกสร้างขึ้น หนุนหลังด้วยสิทธิ์อนุญาตระดับเอกสาร:
// ล็อกค่าตอนสร้างฟิลด์: text field แบบ read-only
Pdf.CurrentPage.AddTextField('CaseNumber', 'BC-2026-0117',
Rect(50, 700, 220, 720), 0, [ffReadOnly]);
// เผื่อไว้สองชั้น: จำกัดการกรอกฟอร์มทั้งเอกสาร
Pdf.ActivateProtection := True;
Pdf.CryptKeyLength := aes256;
Pdf.OwnerPassword := 'records-owner';
Pdf.ProtectOptions := [prPrint, prInformationCopy, prExtractContent];
// ไม่ให้สิทธิ์กรอกฟอร์ม: prFillAnnotations ไม่อยู่ใน set นี้
ต้องเข้าใจให้ชัดเจนว่าสิ่งนั้นให้อะไรคุณบ้างและไม่ให้อะไร ฟิลด์แบบ read-only ก็ยังคงเป็น form object อยู่ดี มันปรากฏอยู่ในแผงฟิลด์ของ viewer ค่าของมันอ่านได้ผ่าน form API และเครื่องมือที่เขียนไฟล์ใหม่สามารถล้าง read-only flag ออกได้อีกครั้ง permission flag ยกระดับมาตรฐานขึ้นแต่ก็ขึ้นอยู่กับว่า viewer เลือกที่จะเคารพมันหรือไม่ ซึ่งเป็นข้อจำกัดที่ ISO 32000-1 ระบุไว้อย่างชัดเจน เมื่อหน่วยงานกำกับดูแลยืนกรานว่าบันทึกที่เก็บถาวรต้องไม่มี form object อยู่เลย คำตอบที่ตรงไปตรงมาสำหรับ HotPDF ในตอนนี้คือการสร้างเอกสารขึ้นมาใหม่: อ่านค่าออกมา แล้ววาดมันเป็นเนื้อหา TextOut ธรรมดาบนหน้าใหม่ แทนที่จะแต่งตัว read-only flag ให้ดูเหมือนเป็นการ flatten สิ่งหนึ่งที่ต้องจำไว้บนเส้นทางของ permission คือ CryptKeyLength ต้องถูกตั้งค่าก่อน BeginDoc เสมอ ส่วนที่เหลืออยู่ในบทความเรื่องการเข้ารหัส AES-256 และสิทธิ์อนุญาตของเรา
XFA หมายความว่าอย่างไรสำหรับความสอดคล้องด้านการเก็บถาวร
ทั้ง PDF/A และ PDF/X ปฏิเสธ XFA โดยสิ้นเชิง ดังนั้น pipeline ที่ป้อนข้อมูลเข้า archive แบบ ISO 19005 จึงต้องแปลงก่อนเสมอ และลำดับนี้ต่อรองไม่ได้: load, FlattenLoadedXFA, save แล้วจึงรัน archival generation หรือ validation กับผลลัพธ์ AcroForm อย่าถือว่าการแปลงเป็นข้อพิสูจน์ความสอดคล้องตามมาตรฐาน มันแก้แค่โมเดลฟอร์มและปล่อยฟอนต์ สี และ metadata ไว้เหมือนเดิมทุกประการ ดังนั้นให้ validate output ด้วย veraPDF ก่อนที่จะเชื่อมัน เมื่อฟอร์มอยู่ฝั่ง AcroForm แล้ว พฤติกรรมของมันก็มีชุดการควบคุมเป็นของตัวเอง JavaScript trigger, submit action และ validation script อธิบายไว้ในบทความเรื่องฟิลด์และ action ของ AcroForm ใน HotPDF
API ด้านการลงทะเบียน XFA, การแปลง และฟอร์มที่แสดงในบทความนี้มาพร้อมกับHotPDF Delphi Componentสำหรับ Delphi และ C++Builder ซึ่งเอกสารประกอบติดตามชุดฟีเจอร์ XFA ตามที่มันเติบโตขึ้นในรุ่นล่าสุดต่าง ๆ