XFA, XML Forms Architecture, är föråldrat. ISO 32000-1 har med det i §12.7 med anmärkningen att det har tagits bort från PDF 2.0, och moderna visningsprogram släpper sina XFA-motorer en efter en. Inget av det har tömt arkiven. Statliga intagsformulär, försäkringsansökningar och kontoutdrag skapades som XFA under större delen av två decennier, och dessa filer anländer fortfarande i inkorgar och dokumentflöden idag. När visningsprogrammet som brukade rendera dem slutar göra det, förvandlas formuläret till en tom sida med en platshållare som ber dig att "öppna i en annan läsare". Den hållbara lösningen är att platta till (flatten) XFA till statiskt PDF-innehåll som vilken läsare som helst kan rita.
Den svåra delen av den plattläggningen är inte fälten. Textrutor och kryssrutor mappar till AcroForm-komponenter tillräckligt rent. Den svåra delen är den rika texten (rich text) som XFA lagrar inuti ett ritelement (draw element), i ett <exData contentType="text/html">-block. Det blocket är en HTML-delmängd med inbäddad formatering (inline styling) och, ofta, länkar (anchors). Att få över det på sidan innebär att återskapa både den formaterade texten och de aktiva hyperlänkarna, och hyperlänkarna är där de flesta implementeringar tyst ger upp.
Hur XFA-rik-text faktiskt ser ut
En exData-kropp är en liten del av XHTML. Ett stycke är ett <p>; ett formaterat teckenintervall är en <span> med sin egen inbäddade CSS för fetstil, kursiv stil, färg och storlek; och en hyperlänk är en <a href="..."> som omsluter sin synliga text. En enskild rad kan innehålla flera span-taggar i rad, var och en med olika formatering, och en av dem kan vara en länk. Formateringen är inte dekoration som kan tas bort. En klausul som renderas i fet röd stil för att det är en juridisk varning måste förbli fet och röd efter plattläggningen, annars representerar det plattade dokumentet inte originalet korrekt.
Så plattläggningsmotorn kan inte behandla blocket som en enda sträng. Den måste vandra genom den inbäddade strukturen, lösa upp varje körnings (run) effektiva stil genom att lägga span-taggens inbäddade CSS över ritelementets bas-typsnitt, och lägga ut körningarna efter varandra över raden. HotPDF modellerar vart och ett av dessa utlagda fragment som en intern TXFARichRun-post. Posten bär körningens text, dess upplösta stil, dess uppmätta ruta och, för en länk, den Href den pekar på.
Att lägga ut körningarna från vänster till höger
Positionering är där rik text slutar vara ett tolkningsproblem (parsing) och blir ett typsättningsproblem. Körningarna delar en rad, så varje körning börjar där den föregående slutade. Det finns ingen uppmärkning som registrerar dessa positioner; de måste mätas. Motorns interna LayoutRichText-rutin mäter varje körning med samma typsnittsmetrik som senare ska rita den, och ställer sedan in körningens horisontella förskjutning (offset) till den löpande summan av alla tidigare körningsbredder. Körning ett börjar vid ritrutans origo, körning två börjar vid bredden av körning ett, körning tre vid den kombinerade bredden av de första två, och så vidare över raden.
Det är därför typsnittsjustering vid mätning betyder så mycket. Layout-steget mäter frammatningar (advances); ett separat renderingssteg ritar glyfer. Om de två stegen är oense om typsnittet kommer rutorna som layouten beräknade inte att ligga under glyferna som renderaren ritar. HotPDF håller dem i takt genom att mappa varje körnings upplösta stil till en typsnittsspecifikation, via den interna hjälparen RunStyleToFontSpec, som matchar renderarens egna standardvärden för Arial på 10 punkter. Den uppmätta frammatningen och den ritade texten stämmer då överens, och en körnings beräknade ruta täcker verkligen de tecken som en läsare ser.
// 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;
Från en länk-körning till en PDF-länkannotering
En hyperlänk i en färdig PDF är inte en del av sidinnehållet. Det är ett separat objekt, en länkannotering (Link annotation), beskriven i ISO 32000-1 §12.5.6.5. Annoteringen har en /Rect som definierar den klickbara rektangeln på sidan och en åtgärd som utlöses när rektangeln klickas. För en extern länk är åtgärden en URI-åtgärd: /S /URI med måladressen som sin /URI-sträng. Den synliga texten under är vanligt sidinnehåll; annoteringen är den osynliga aktiva zonen som lagts över den.
Plattläggningssökvägen följer exakt den modellen. När en körning bär en Href, ritar HotPDF först den formaterade texten och bygger sedan en länkannotering över körningens ruta. Den publika startpunkten för den annoteringen är sidmetoden AddURILink, som skapar objektet /Type /Annot /Subtype /Link med en /URI-åtgärd och returnerar annoteringsordboken. Dess rektangel is körningens uppmätta ruta, översatt från ritelementets lokala koordinater till sidkoordinater. Resultatet är en länk som landar exakt på länktexten (anchor text) och ingen annanstans.
// 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;
Varför träffytan måste komma från uppmätta bredder
Det är frestande att tänka sig att lokalisera länken genom att söka på sidan efter dess synliga text och rita rektangeln runt det som hittas. Det fungerar inte, och orsaken är grundläggande för hur plattad text lagras. De formaterade körningarna målas med inbäddade delmängdstypsnitt (subset fonts). Ett delmängdstypsnitt numrerar om glyferna det behåller, så sidans innehållsström innehåller hexadecimala CID-koder, inte de ursprungliga teckenkoderna. Byten på sidan är inte de bokstäver en människa läser, och de är inte sökbara som text. En sökning efter länkens rubrik hittar ingenting, eftersom den rubriken inte existerar som bokstavlig text någonstans i strömmen.
Det enda pålitliga fästet för rektangeln är den geometri som layout-steget redan har producerat. Varje körnings förskjutning och uppmätta bredd beräknades när linjen flödade, innan någon glyf numrerades om, och de beskriver var texten fysiskt kommer att visas. HotPDF tar därför länkrektangeln direkt från körningens utlagda ruta snarare än från någon textsökning. Eftersom mätningen använde renderings-typsnittet är rutan korrekt oavsett delmängdsskapande. Geometri överlever kodningen; text gör det inte. Det är hela argumentet för positionering med uppmätt bredd, och det är därför en plattläggare som försöker eftermontera länkar via textsökning producerar träffzoner som driver iväg eller försvinner.
Att driva plattläggningen från din kod
För en PDF som redan innehåller ett XFA-paket är startpunkten FlattenLoadedXFA. Läs in dokumentet, anropa metoden och spara resultatet. Parametern Editable avgör vad som händer med formulärfälten: skicka True för att behålla dem som ifyllbara AcroForm-komponenter (widgets), eller False för att markera varje komponent som skrivskyddad så att utdatan blir en frusen post. Ritblocken med rik text, med sina formaterade körningar och länkannoteringar, produceras i båda fallen. Funktionen returnerar antalet komponenter som skickades ut.
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;
Läs alltid av XFAFlattenWarnings efter anropet. Listan rensas i början av varje plattläggning och samlar en rad för varje element som motorn avböjde att rendera: en fälttyp som inte stöds, en ritad bild som inte kunde avkodas, ett exData-block utan användbara span-taggar. Inget av detta kastar ett undantag, så en tom varningslista är ditt bevis på att allt mappades, och en icke-tom talar om för dig exakt vilka original du ska inspektera. När du har den råa XFA:n som XDP-byte snarare än en inläst PDF, tar syskonmetoden ApplyXFAAsAcroForm tar disse byte direkt och delar samma kodväg och samma varningsbeteende. Den kompletterande metoden AddXFAPacket går åt andra hållet och bäddar in ett XFA-paket i ett dokument du bygger.
Att bekräfta resultatet i en läsare
Öppna den plattade filen i Acrobat, eller valfritt aktuellt visningsprogram, och kontrollera två saker. För det första att den rika texten renderades med sin formatering intakt: de feta körningarna är feta, de färgade bär sin färg och span-taggarna ligger i rätt ordning på raden istället för att överlappa eller hamna utanför rutan. För det andra att hyperlänkarna är aktiva. Håll muspekaren över en länk och statusfältet ska visa måladressen; klicka på den och URI-åtgärden ska öppna den. Använd visningsprogrammets annoteringsinspektör för att bekräfta att var och en är en äkta /Link-annotering vars /Rect omsluter länktexten, liggande över innehåll som nu är vanliga ritade glyfer snarare än formulärrenderad XFA. Den kombinationen, formaterad statisk text plus verkliga Link-annoteringar på rätt rektanglar, är vad som gör att det plattade dokumentet överlever de XFA-motorer det inte längre behöver.
Att platta till själva fälten, textrutorna, kryssrutorna och valistorna som omger den här rika texten, täcks i vår genomgång om att platta till XFA-formulär till AcroForm-komponenter. För den bredare historien om att bygga och placera Link-annoteringar för hand, utöver de som plattläggningssökvägen genererar, se att arbeta med PDF-annoteringar i HotPDF. Båda bygger på samma annoterings- och formulärmodell som levereras med HotPDF Component för Delphi och C++Builder.