XFA หรือ XML Forms Architecture ถูกยกเลิกแล้ว ISO 32000-1 มีมันใน §12.7 พร้อมหมายเหตุว่ามันถูกลบออกจาก PDF 2.0 และ viewers สมัยใหม่กำลังยกเลิก XFA engines ทีละตัว ไม่มีสิ่งใดทำให้ archives ว่างเปล่า แบบฟอร์มรับเข้าของรัฐบาล, ใบสมัครประกัน และใบแจ้งยอดธนาคารถูก author เป็น XFA มาเกือบสองทศวรรษ และไฟล์เหล่านั้นยังคงมาถึง inboxes และ document pipelines ในปัจจุบัน เมื่อ viewer ที่เคย render มันหยุดทำเช่นนั้น แบบฟอร์มจะกลายเป็นหน้าว่างพร้อม placeholder "โปรดเปิดใน reader อื่น" การแก้ไขที่ทนทานคือการ flatten XFA เป็น static PDF content ที่ reader ใดก็สามารถ paint ได้
ส่วนที่ยากของการ flattening นั้นไม่ใช่ fields text boxes และ check boxes จับคู่กับ AcroForm widgets ได้สะอาดพอ ส่วนที่ยากคือ rich text ที่ XFA เก็บไว้ภายใน draw element ใน <exData contentType="text/html"> block บล็อกนั้นคือ HTML subset ที่มี inline styling และมักมี anchors การนำมันไว้บนหน้ากระดาษหมายถึงการทำซ้ำทั้ง styled text และ live hyperlinks และ hyperlinks คือจุดที่ implementations ส่วนใหญ่ยอมแพ้อย่างเงียบ ๆ
XFA rich text ดูเหมือนอะไรจริง ๆ
body ของ exData คือ XHTML ชิ้นเล็ก ๆ paragraph คือ <p> span ที่มี styled characters คือ <span> ที่มี inline CSS ของตัวเองสำหรับ weight, posture, color และ size และ hyperlink คือ <a href="..."> ที่ wrap ข้อความที่มองเห็นได้ บรรทัดเดียวสามารถมีหลาย spans ต่อกัน แต่ละอันมี styling ต่างกัน และหนึ่งในนั้นสามารถเป็น anchor ได้ styling ไม่ใช่การตกแต่งที่สามารถลดทอนได้ ข้อความใน bold red เพราะเป็นคำเตือนทางกฎหมายต้องยังคง bold และ red หลังจาก flattening มิเช่นนั้นเอกสารที่ flatten แล้วจะแสดงเนื้อหาต้นฉบับไม่ถูกต้อง
ดังนั้น flatten engine ไม่สามารถปฏิบัติต่อ block เป็น string เดียว มันต้องเดินโครงสร้าง inline แก้ไข effective style ของแต่ละ run โดยการซ้อน inline CSS ของ span ทับ base font ของ draw element และวาง runs ต่อกันตลอดทั้งบรรทัด HotPDF จำลอง fragments ที่วางไว้เหล่านี้เป็น TXFARichRun record ภายใน record นั้นมีข้อความของ run, resolved style, measured box และสำหรับ anchor คือ Href ที่มันชี้ไป
การวาง runs จากซ้ายไปขวา
การวางตำแหน่งคือจุดที่ rich text หยุดเป็นปัญหาการ parsing และกลายเป็นปัญหาการเรียงพิมพ์ runs ใช้บรรทัดร่วมกัน ดังนั้นแต่ละ run เริ่มต้นที่ที่ run ก่อนหน้าสิ้นสุด ไม่มี markup ที่บันทึกตำแหน่งเหล่านั้น ต้องวัด routine ภายใน LayoutRichText ของ engine วัด run ทุก run ด้วย font metrics เดียวกับที่จะ paint ในภายหลัง จากนั้นตั้งค่า horizontal offset ของ run เป็นผลรวมสะสมของความกว้าง run ก่อนหน้าทั้งหมด Run หนึ่งเริ่มที่ draw box origin run สองเริ่มที่ความกว้างของ run หนึ่ง run สามที่ความกว้างรวมของสองตัวแรก และต่อไปเรื่อย ๆ ตลอดทั้งบรรทัด
นี่คือเหตุผลที่ measurement font alignment มีความสำคัญมาก pass layout วัด advances, pass render แยกต่างหากวาด glyphs ถ้า two passes ไม่เห็นด้วยกันเกี่ยวกับ font, boxes ที่ layout คำนวณจะไม่อยู่ใต้ glyphs ที่ renderer วาด HotPDF รักษาให้มันสอดคล้องกันโดย mapping resolved style ของแต่ละ run กับ font specification ผ่าน RunStyleToFontSpec helper ภายใน ที่ตรงกับค่าเริ่มต้นของ renderer ซึ่งก็คือ Arial ที่ 10 points advance ที่วัดได้และข้อความที่วาดจึงตกลงกัน และ box ที่คำนวณของ run ครอบคลุมตัวอักษรที่ผู้อ่านเห็นจริง ๆ
// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
TRichRunInfo = record
Dx, Dy : Double; // top-left, relative to the draw-box origin
W, H : Double; // measured run box (width from the layout pass)
Text : AnsiString; // the run's visible characters
Href : AnsiString; // URI target for an <a> run, '' otherwise
end;
จาก anchor run ไปยัง PDF Link annotation
hyperlink ใน PDF ที่เสร็จสมบูรณ์ไม่ใช่ส่วนหนึ่งของ page content มันคือ object แยกต่างหาก, Link annotation, ที่อธิบายไว้ใน ISO 32000-1 §12.5.6.5 annotation มี /Rect ที่กำหนด clickable rectangle บนหน้าและ action ที่ fires เมื่อ rectangle ถูกคลิก สำหรับ external link action คือ URI action: /S /URI พร้อม target address เป็น /URI string ข้อความที่มองเห็นข้างใต้คือ ordinary page content annotation คือ invisible hot zone ที่วางทับมัน
เส้นทาง flatten ตามแบบจำลองนี้พอดี เมื่อ run มี Href HotPDF ก่อนอื่นวาด styled text แล้วสร้าง Link annotation บน box ของ run จุดเข้าสาธารณะสำหรับ annotation นั้นคือ page method AddURILink ซึ่งสร้าง /Type /Annot /Subtype /Link object ที่มี /URI action และคืน annotation dictionary สี่เหลี่ยมของมันคือ measured box ของ run, แปลงจาก local coordinates ของ draw element เป็น page coordinates ผลลัพธ์คือ link ที่ตกอยู่บน anchor text อย่างแม่นยำและไม่ที่อื่น
// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // page-space hit box for the run
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
เหตุใด hit box จึงต้องมาจากความกว้างที่วัดได้
เป็นเรื่องน่าล่อใจที่จะจินตนาการถึงการค้นหา link โดยการค้นหา visible text บนหน้าและวาง rectangle รอบสิ่งที่พบ แต่มันไม่ได้ผล และเหตุผลนั้นเป็นพื้นฐานสำหรับวิธีการเก็บข้อความที่ flatten แล้ว styled runs ถูก paint ด้วย embedded subset fonts subset font จะนับหมาย glyphs ที่มันเก็บไว้ใหม่ ดังนั้น page content stream มี hexadecimal CID codes ไม่ใช่ character codes ดั้งเดิม bytes บนหน้าไม่ใช่ตัวอักษรที่มนุษย์อ่านและไม่สามารถค้นหาเป็นข้อความได้ การค้นหา caption ของ anchor ไม่พบอะไร เพราะ caption นั้นไม่มีอยู่เป็น literal text ที่ใดในหน้า stream
สมอที่เชื่อถือได้สำหรับ rectangle มีเพียงเรขาคณิตที่ layout pass สร้างขึ้นแล้ว offset และความกว้างที่วัดของแต่ละ run ถูกคำนวณขณะ flow บรรทัดก่อนที่ glyph ใดจะถูกนับใหม่ และอธิบายตำแหน่งที่ข้อความจะปรากฏทางกายภาพ HotPDF จึงนำ link rectangle จาก laid-down box ของ run โดยตรงแทนที่จะค้นหาข้อความใดๆ เพราะการวัดใช้ render font box จึงถูกต้องโดยไม่คำนึงถึงการ subsetting เรขาคณิตรอดจากการเข้ารหัส ข้อความไม่รอด นั่นคือข้อโต้แย้งทั้งหมดสำหรับ measured-width positioning และนั่นคือเหตุผลที่ flattener ที่พยายาม retrofit links โดยการค้นหาข้อความสร้าง hit zones ที่ drift หรือหายไป
การขับเคลื่อน flatten จาก code ของคุณ
สำหรับ PDF ที่มี XFA packet อยู่แล้ว จุดเข้าคือ FlattenLoadedXFA โหลดเอกสาร เรียก method และบันทึกผลลัพธ์ parameter Editable ตัดสินว่าจะเกิดอะไรกับ form fields: ส่ง True เพื่อเก็บไว้เป็น fillable AcroForm widgets หรือ False เพื่อทำเครื่องหมาย widget ทุกตัวว่า read-only ให้ output เป็นบันทึกที่ frozen rich-text draw blocks ที่มี styled runs และ link annotations ถูกสร้างขึ้นไม่ว่าจะด้วยวิธีใด function คืนจำนวน widgets ที่ emit
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True keeps fields fillable; False freezes them read-only.
Emitted := Pdf.FlattenLoadedXFA(True);
// Anything the engine could not map is reported, not raised.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
อ่าน XFAFlattenWarnings เสมอหลังจากการเรียก รายการถูกล้างที่จุดเริ่มต้นของ flatten แต่ละครั้งและสะสมบรรทัดสำหรับทุก element ที่ engine ปฏิเสธที่จะ render: field kind ที่ไม่รองรับ, draw image ที่ไม่สามารถ decode ได้, exData block ที่ไม่มี spans ที่ใช้ได้ ไม่มีสิ่งใดเหล่านั้น raise exception ดังนั้นรายการ warnings ว่างเปล่าคือหลักฐานว่าทุกอย่าง map และรายการที่ไม่ว่างเปล่าบอกคุณว่าต้นฉบับใดที่ควรตรวจสอบ เมื่อคุณมี raw XFA เป็น XDP bytes แทนที่จะเป็น PDF ที่โหลดแล้ว sibling method ApplyXFAAsAcroForm รับ bytes เหล่านั้นโดยตรงและใช้ code path เดียวกันและพฤติกรรม warnings เดียวกัน method ที่เสริมกัน AddXFAPacket ทำตรงข้าม โดยฝัง XFA packet ลงในเอกสารที่คุณกำลังสร้าง
การยืนยันผลลัพธ์ใน reader
เปิดไฟล์ที่ flatten แล้วใน Acrobat หรือ viewer ปัจจุบันใดก็ได้ และตรวจสอบสองสิ่ง ก่อนคือ rich text render พร้อม styling ที่สมบูรณ์: runs ที่ bold ยังคง bold, runs ที่มี color มีสี และ spans อยู่ในลำดับที่ถูกต้องบนบรรทัดแทนที่จะทับซ้อนกันหรือวิ่งออกนอก box ประการที่สองคือ hyperlinks ใช้งานได้จริง hover บน anchor และ status bar ควรแสดง target address, คลิกและ URI action ควรเปิดมัน ใช้ annotation inspector ของ viewer เพื่อยืนยันว่าแต่ละอันเป็น /Link annotation จริง ๆ ที่ /Rect กอด anchor text อยู่บน content ที่ตอนนี้เป็น plain painted glyphs ไม่ใช่ form-rendered XFA ชุดนั้นซึ่งก็คือ styled static text บวก real Link annotations บน rectangles ที่ถูกต้องคือสิ่งที่ทำให้เอกสารที่ flatten แล้วมีชีวิตยืนยาวกว่า XFA engines ที่มันไม่ต้องการอีกต่อไป
การ flatten fields เอง ซึ่งก็คือ text boxes, check boxes และ choice lists ที่ล้อมรอบ rich text นี้ ครอบคลุมอยู่ใน คู่มือของเราเกี่ยวกับการ flatten XFA forms เป็น AcroForm widgets สำหรับเรื่องราวที่กว้างขึ้นของการสร้างและวาง Link annotations ด้วยตนเอง นอกเหนือจากที่ flatten path สร้างขึ้น ดู การทำงานกับ PDF annotations ใน HotPDF ทั้งสองสร้างบน annotation และ forms model เดียวกับที่ ship มาพร้อมกับ HotPDF Component สำหรับ Delphi และ C++Builder