XFA, ארכיטקטורת טופסי ה-XML, הוצאה משימוש. תקן ISO 32000-1 נושא אותה בסעיף 12.7 עם ההערה שהיא הוסרה מ-PDF 2.0, וצופים מודרניים נוטשים את מנועי ה-XFA שלהם בזה אחר זה. שום דבר מזה לא רוקן את הארכיונים. טופסי קליטה ממשלתיים, בקשות לביטוח, ודוחות בנקאיים נכתבו כ-XFA במשך רוב שני העשורים האחרונים, וקבצים אלו עדיין מגיעים לתיבות דואר נכנס ולצינורות מסמכים היום. כאשר הצופה ששימש לרינדור שלהם מפסיק לעשות זאת, הטופס הופך לעמוד ריק עם מציין מיקום (placeholder) של "אנא פתח בקורא שונה". התיקון העמיד הוא להשטח את ה-XFA לתוכן PDF סטטי שכל קורא יכול לצייר
החלק הקשה בהשטחה זו אינו השדות. תיבות טקסט ותיבות סימון ממופות לווידג'טים של AcroForm בצורה נקייה מספיק. החלק הקשה הוא הטקסט העשיר ש-XFA מאחסן בתוך אלמנט draw (ציור), בבלוק <exData contentType="text/html">. בלוק זה הוא תת-קבוצה של HTML עם עיצוב מוטבע (inline) ולעיתים קרובות עוגנים. הבאתו אל העמוד משמעותה שחזור גם של הטקסט המעוצב וגם של ההיפר-קישורים החיים, וההיפר-קישורים הם המקום שבו רוב המימושים מוותרים בשקט
איך באמת נראה טקסט עשיר של XFA
גוף exData הוא פרוסה קטנה של XHTML. פסקה היא <p>; מקטע תווים מעוצב הוא <span> עם CSS מוטבע משלו עבור משקל, תנוחה, צבע וגודל; והיפר-קישור הוא <a href="..."> העוטף את הטקסט הגלוי שלו. שורה בודדת יכולה להכיל מספר מקטעים ברצף, כל אחד עם עיצוב שונה, ואחד מהם יכול להיות עוגן. העיצוב אינו קישוט שניתן להשמיט. סעיף המרונדר באדום מודגש מכיוון שהוא אזהרה משפטית חייב להישאר אדום ומודגש לאחר ההשטחה, אחרת המסמך המושָטח מציג את המקור בצורה שגויה
לכן מנוע ההשטחה אינו יכול להתייחס לבלוק כמחרוזת אחת. עליו ללכת לאורך המבנה המוטבע, לפתור את הסגנון האפקטיבי של כל ריצה על ידי יצירת שכבות של ה-CSS המוטבע של המקטע מעל גופן הבסיס של אלמנט ה-draw, ולפרוס את הריצות זו אחר זו לאורך השורה. HotPDF ממדל כל אחד מהפרגמנטים הפרוסים הללו כרשומת TXFARichRun פנימית. הרשומה נושאת את הטקסט של הריצה, הסגנון הפתור שלה, התיבה הנמדדת שלה, ועבור עוגן, ה-Href אליו הוא מצביע
פריסת הריצות משמאל לימין
מיקום הוא המקום שבו טקסט עשיר מפסיק להיות בעיית ניתוח (parsing) והופך לבעיית סדר (typesetting). הריצות חולקות שורה, כך שכל ריצה מתחילה היכן שהקודמת הסתיימה. אין סימון (markup) שמתעד מיקומים אלה; יש למדוד אותם. שגרת ה-LayoutRichText הפנימית של המנוע מודדת כל ריצה עם אותם מדדי גופן שיציירו אותה מאוחר יותר, ואז מגדירה את ההיסט האופקי של הריצה לסכום הרץ של כל רוחבי הריצות הקודמות. ריצה אחת מתחילה במקור תיבת ה-draw, ריצה שתיים מתחילה ברוחב של ריצה אחת, ריצה שלוש ברוחב המשולב של השתיים הראשונות, וכן הלאה לאורך השורה
זו הסיבה שיישור הגופן במדידה כה חשוב. מעבר הפריסה מודד קידומים (advances); מעבר רינדור נפרד מצייר גליפים. אם שני מעברים אלו חולקים על הגופן, התיבות שהפריסה חישבה לא יישבו תחת הגליפים שהמרנדר מצייר. HotPDF שומר אותם מסונכרנים על ידי מיפוי הסגנון הפתור של כל ריצה למפרט גופן, דרך עוזר RunStyleToFontSpec פנימי, שתואם לברירות המחדל של המרנדר עצמו של Arial בגודל 10 נקודות. הקידום הנמדד והטקסט המצויר מסכימים אז, ותיבה המחושבת של ריצה מכסה באמת את התווים שקורא רואה
// 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;
מריצת עוגן להערת Link של PDF
היפר-קישור ב-PDF מוגמר אינו חלק מתוכן העמוד. הוא אובייקט נפרד, הערת Link (קישור), המתואר בתקן ISO 32000-1 סעיף 12.5.6.5. להערה יש /Rect המגדיר את המלבן הלחיץ בעמוד ופעולה המופעלת כאשר לוחצים על המלבן. עבור קישור חיצוני הפעולה היא פעולת URI: /S /URI כאשר כתובת היעד היא מחרוזת ה-/URI שלה. הטקסט הגלוי שמתחת הוא תוכן עמוד רגיל; ההערה היא האזור החם הבלתי נראה המונח מעליו
נתיב ההשטחה עוקב בדיוק אחר מודל זה. כאשר ריצה נושאת Href, HotPDF מצייר תחילה את הטקסט המעוצב, ואז בונה הערת Link מעל התיבה של הריצה. נקודת הכניסה הציבורית להערה זו היא מתודת העמוד AddURILink, היוצרת את אובייקט ה-/Type /Annot /Subtype /Link עם פעולת /URI ומחזירה את מילון ההערה. המלבן שלה הוא התיבה הנמדדת של הריצה, מתורגמת מהקואורדינטות המקומיות של אלמנט ה-draw לקואורדינטות של העמוד. התוצאה היא קישור שנוחת בדיוק על טקסט העוגן ובשום מקום אחר
// 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;
מדוע תיבת הפגיעה חייבת להגיע מרוחבים נמדדים
מפתה לדמיין את איתור הקישור על ידי חיפוש העמוד אחר הטקסט הגלוי שלו וציור המלבן סביב כל מה שנמצא. זה לא עובד, והסיבה היא מהותית לאופן שבו טקסט מיושר מאוחסן. הריצות המעוצבות מצוירות עם גופני תת-קבוצה (subset) מוטבעים. גופן תת-קבוצה ממספר מחדש את הגליפים שהוא שומר, כך שזרם תוכן העמוד מחזיק קודי CID הקסדצימליים, לא את קודי התווים המקוריים. הבתים בעמוד אינם האותיות שאדם קורא, והם אינם ניתנים לחיפוש כטקסט. חיפוש אחר הכיתוב של העוגן אינו מוצא דבר, מכיוון שכיתוב זה אינו קיים כטקסט מילולי בשום מקום בזרם
העוגן האמין היחיד עבור המלבן הוא הגיאומטריה שמעבר הפריסה כבר ייצר. ההיסט והרוחב הנמדד של כל ריצה חושבו בזמן הזרמת השורה, לפני שגליף כלשהו מוספר מחדש, והם מתארים היכן הטקסט יופיע פיזית. HotPDF לוקח לכן את מלבן הקישור ישר מהתיבה המונחת של הריצה ולא מאיזשהו חיפוש טקסט. מכיוון שהמדידה השתמשה בגופן הרינדור, התיבה נכונה ללא קשר ליצירת תת-הקבוצות. הגיאומטריה שורדת את הקידוד; טקסט לא. זהו כל הטיעון בעד מיקום לפי רוחב-נמדד, וזו הסיבה שמשטח (flattener) שמנסה להתאים בדיעבד קישורים על ידי חיפוש טקסט מייצר אזורי פגיעה שנסחפים או נעלמים
הנעת ההשטחה מהקוד שלך
עבור PDF שכבר מכיל חבילת XFA, נקודת הכניסה היא FlattenLoadedXFA. טען את המסמך, קרא למתודה ושמור את התוצאה. הפרמטר Editable מחליט מה קורה לשדות הטופס: העבר True כדי לשמור אותם כווידג'טים ניתנים למילוי של AcroForm, או False כדי לסמן כל וידג'ט כקריאה-בלבד כך שהפלט יהיה רשומה מוקפאת. בלוקי ה-draw של טקסט עשיר, עם הריצות המעוצבות שלהן והערות הקישור, מיוצרים בכל מקרה. הפונקציה מחזירה את ספירת הווידג'טים שהיא פלטה
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 לאחר הקריאה. הרשימה מנוקה בתחילת כל השטחה וצוברת שורה עבור כל אלמנט שהמנוע סירב לרנדר: סוג שדה שאינו נתמך, תמונת draw שלא הצליחה להיות מפוענחת, בלוק exData ללא מקטעים שמישים. אף אחד מאלה אינו מעלה חריגה (exception), כך שרשימת אזהרות ריקה היא העדות שלך שהכל מופה, ורשימה שאינה ריקה אומרת לך בדיוק אילו מקורות עליך לבדוק. כאשר אתה מחזיק את ה-XFA הגולמי כבתי XDP במקום כ-PDF נטען, המתודה האחות ApplyXFAAsAcroForm לוקחת בתים אלה ישירות וחולקת את אותו נתיב קוד ואת אותה התנהגות אזהרות. המתודה המשלימה AddXFAPacket הולכת בכיוון ההפוך, ומטמיעה חבילת XFA לתוך מסמך שאתה בונה
אישור התוצאה בקורא
פתח את הקובץ המושָטח ב-Acrobat, או בכל צופה נוכחי, ובדוק שני דברים. ראשית, הטקסט העשיר המרונדר עם עיצובו בשלמותו: הריצות המודגשות הן מודגשות, הריצות הצבעוניות נושאות את צבען, והמקטעים יושבים בסדר הנכון על השורה במקום לחפוף או לחרוג מהתיבה. שנית, ההיפר-קישורים חיים. רחף מעל עוגן ושורת המצב אמורה להציג את כתובת היעד; לחץ עליו ופעולת ה-URI אמורה לפתוח אותו. השתמש במפקח ההערות של הצופה כדי לוודא שכל אחת היא הערת /Link מקורית שה-/Rect שלה מחבק את טקסט העוגן, ויושב מעל תוכן שכעת הוא גליפים צבועים רגילים ולא XFA המרונדר כטופס. שילוב זה, טקסט סטטי מעוצב בתוספת הערות Link אמיתיות על המלבנים הנכונים, הוא מה שגורם למסמך המושָטח להאריך ימים יותר ממנועי ה-XFA שהוא כבר אינו זקוק להם
השטחת השדות עצמם, תיבות הטקסט, תיבות הסימון ורשימות הבחירה שמקיפות טקסט עשיר זה, מכוסה בהדרכה שלנו על השטחת טופסי XFA לווידג'טים של AcroForm. לסיפור הרחב יותר של בנייה והצבה של הערות Link ביד, מעבר לאלו שנתיב ההשטחה מייצר, ראה עבודה עם הערות PDF ב-HotPDF. שניהם נבנים על אותו מודל הערות וטפסים הכלול ברכיב HotPDF עבור Delphi ו-C++Builder