XFA, XML Forms Architecture, är föråldrat. ISO 32000-1 bär det i §12.7 med notan att det är borttaget från PDF 2.0, och moderna visare tappar sina XFA-motorer en efter en. Inget av det har tömt arkiven. Statliga intagningsformulär, försäkringsansökningar och bankutdrag skapades som XFA under en god del av två decennier, och dessa filer anländer fortfarande i inkorgar och dokumentpipelines idag. När visaren som brukade rendera dem slutar göra det förvandlas formuläret till en tom sida med en "öppna i en annan läsare"-platshållare. Den hållbara lösningen är att förenkla XFA till statiskt PDF-innehåll som vilken läsare som helst kan visa
Den svåra delen av den förenklingen är inte fälten. Textrutor och kryssrutor mappas till AcroForm-widgetar tillräckligt rent. Den svåra delen är den rich text som XFA lagrar inuti ett draw-element, i ett <exData contentType="text/html">-block. Det blocket är en HTML-delmängd med inline-styling och, ofta, ankarlänkar. Att få det på sidan innebär att reproducera både den stilade texten och de levande hyperlänkarna, och hyperlänkarna är där de flesta implementationer tyst ger upp
Hur XFA rich text faktiskt ser ut
En exData-kropp är en liten del av XHTML. Ett stycke är ett <p>; ett stilat teckenavsnitt är ett <span> med sin egen inline-CSS för vikt, ställning, färg och storlek; och en hyperlänk är ett <a href="..."> som omsluter sin synliga text. En enskild rad kan hålla flera avsnitt i rad, var och en med olika styling, och ett av dem kan vara ett ankarlänk. Stylingen är inte dekoration som kan tas bort. En klausul renderad i fetstil rött för att det är en juridisk varning måste förbli fetstil och röd efter förenkling, eller det förenklade dokumentet felavskiljer det ursprungliga
Så förenklingmotorn kan inte behandla blocket som en sträng. Den måste gå igenom inline-strukturen, lösa varje körnings effektiva stil genom att lagra span:ens inline-CSS över draw-elementets basfonts, och lägga ut körningarna en efter en längs raden. HotPDF modellerar var och en av dessa utlagda fragment som en intern TXFARichRun-post. Posten bär körningens text, dess lösta stil, dess uppmätta box och, för en ankarlänk, den Href den pekar på
Att lägga ut körningarna vänster till höger
Positionering är där rich text slutar vara ett tolkningsproblem och blir ett sättningstypografiproblem. Körningarna delar en rad, så varje körning börjar där den föregående slutade. Det finns inget märkspråk som registrerar dessa positioner; de måste mätas. Motorns interna LayoutRichText-rutin mäter varje körning med samma typsnittsmätvärden som senare ska måla den, ställer sedan in körningens horisontella förskjutning till den löpande summan av alla tidigare körningsbredder. Körning ett börjar vid draw-boxens ursprung, 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 längs raden
Det är därför mättypsnittsöverensstämmelse spelar så stor roll. Layoutpasset mäter framsteg; ett separat renderpasset ritar glyfer. Om dessa två pass är oense om typsnittet, kommer de boxar som layouten beräknade inte att sitta under de glyfer renderaren målar. HotPDF håller dem i steg genom att mappa varje körnings lösta stil till en typsnittsspesifikation, via den interna RunStyleToFontSpec-hjälparen, som matchar renderarens egna standarder av Arial vid 10 punkter. Det uppmätta framsteget och den ritade texten stämmer sedan överens, och en körnings beräknade box täcker genuint de tecken 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 ankarkörning till en PDF Link-annotation
En hyperlänk i en färdig PDF är inte en del av sidinnehållet. Det är ett separat objekt, en Link-annotation, beskriven i ISO 32000-1 §12.5.6.5. Annotationen har en /Rect som definierar den klickbara rektangeln på sidan och en åtgärd som avfyras 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; annotationen är den osynliga heta zonen lagd över den
Förenklingsvägen följer exakt denna modell. När en körning bär ett Href, ritar HotPDF först den stilade texten, sedan bygger en Link-annotation över körningens box. Den offentliga ingångspunkten för den annotationen är sidmetoden AddURILink, som skapar /Type /Annot /Subtype /Link-objektet med en /URI-åtgärd och returnerar annotationsordboken. Dess rektangel är körningens uppmätta box, översatt från draw-elementets lokala koordinater till sidkoordinater. Resultatet är en länk som landar exakt på ankartexten och ingenstans annars
// 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äffboxen måste komma från uppmätta bredder
Det är frestande att föreställa sig att lokalisera länken genom att söka sidan efter dess synliga text och rita rektangeln runt vad som hittas. Det fungerar inte, och anledningen är grundläggande för hur förenklad text lagras. De stilade körningarna är målade med inbäddade deluppsättningsypsnitt. Ett deluppsättningstypsnitt omnumrerar de glyfer det behåller, så sidinnehållsströmmen hå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 ankarlänkens bildtext hittar ingenting, för den bildtexten finns inte som bokstavlig text någonstans i strömmen
Det enda tillförlitliga ankaret för rektangeln är geometrin som layoutpasset redan producerade. Varje körnings förskjutning och uppmätta bredd beräknades när raden flödades, innan någon glyph omnumrerades, och de beskriver var texten fysiskt kommer att visas. HotPDF tar därför länkrektangeln direkt från körningens utlagda box snarare än från någon textuppslagning. Eftersom mätningen använde rendertypsnittet är boxen korrekt oavsett deluppsättning. Geometri överlever kodningen; text gör det inte. Det är hela argumentet för uppmätt-bredd-positionering, och det är varför en förenklingsenhet som försöker eftermontera länkar via textsökning producerar träffzoner som glider eller försvinner
Att köra förenkling från din kod
För en PDF som redan innehåller ett XFA-paket är ingångspunkten FlattenLoadedXFA. Ladda dokumentet, anropa metoden och spara resultatet. Editable-parametern bestämmer vad som händer med formulärfälten: skicka True för att behålla dem som ifyllbara AcroForm-widgetar, eller False för att markera varje widget som skrivskyddad så att utdata är en fryst post. Rich-text-draw-blocken, med sina stilade körningar och länkanteckningar, produceras oavsett. Funktionen returnerar antalet widgetar den skickade 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 XFAFlattenWarnings efter anropet. Listan rensas i början av varje förenkling och ackumulerar en rad för varje element som motorn vägrade att rendera: en typ av fält som inte stöds, en draw-bild som inte avkodades, ett exData-block utan användbara avsnitt. Ingen av dessa kastar ett undantag, så en tom varningslista är ditt bevis på att allt mappades, och en icke-tom berättar exakt vilka original du ska inspektera. När du håller den rå XFA som XDP-bytes snarare än en laddad PDF tar systermetoden ApplyXFAAsAcroForm dessa bytes direkt och delar samma kodväg och samma varnbeteende. Den kompletterande AddXFAPacket-metoden går åt andra hållet och bäddar in ett XFA-paket i ett dokument du bygger
Bekräfta resultatet i en läsare
Öppna den förenklade filen i Acrobat, eller vilken aktuell visare som helst, och kontrollera två saker. Först att rich text renderades med dess styling intakt: de feta körningarna är feta, de färgade körningarna bär sin färg och avsnitten sitter i rätt ordning på raden snarare än att överlappa varandra eller rinna utanför boxen. För det andra att hyperlänkarna är aktiva. Håll muspekaren över ett ankar och statusfältet ska visa måladressen; klicka på det och URI-åtgärden ska öppna det. Använd visarens annotationsinspektor för att bekräfta att var och en är en äkta /Link-annotation vars /Rect omsluter ankartexten, sittande över innehåll som nu är vanliga målade glyfer snarare än formulärrenderad XFA. Den kombinationen, stilad statisk text plus riktiga Link-annotationer på rätt rektanglar, är vad som gör det förenklade dokumentet till att överleva de XFA-motorer det inte längre behöver
Att förenkla fälten själva, textrutorna, kryssrutorna och valllistorna som omger den rich texten, täcks i vår genomgång av att förenkla XFA-formulär till AcroForm-widgetar. För den bredare berättelsen om att bygga och placera Link-annotationer manuellt, utöver de som förenklingsvägen genererar, se att arbeta med PDF-annotationer i HotPDF. Båda bygger på samma annotations- och formulärmodell som levereras med HotPDF Component för Delphi och C++Builder