XFA, XML Forms Architecture, er utdatert. ISO 32000-1 har den med i §12.7 med en merknad om at den er fjernet fra PDF 2.0, og moderne visningsprogrammer fjerner sine XFA-motorer én etter én. Ingenting av dette har tømt arkivene. Offentlige skjemaer, forsikringssøknader og kontoutskrifter ble opprettet som XFA i nesten to tiår, og disse filene ankommer fortsatt i innbokser og dokumentbehandlingslinjer i dag. Når visningsprogrammet som pleide å gjengi dem slutter å gjøre det, blir skjemaet til en blank side med en "vennligst åpne i en annen leser"-plassholder. Den varige løsningen er å flate ut XFA-en til statisk PDF-innhold som alle lesere kan tegne.
Den vanskelige delen av denne flatingen er ikke feltene. Tekstbokser og avmerkingsbokser avbildes til AcroForm-skjermelementer (widgets) greit nok. Den vanskelige delen er formateringsteksten (rich text) som XFA lagrer i et tegneelement, i en <exData contentType="text/html">-blokk. Den blokken er et HTML-delsett med innebygd styling og ofte ankre (hyperlenker). Å få det inn på siden betyr å reprodusere både den stylede teksten og de levende hyperlenkene, og hyperlenkene er der de fleste implementeringer stille gir opp.
Hvordan XFA formateringstekst faktisk ser ut
En exData-kropp er en liten del av XHTML. Et avsnitt er et <p>; et stylet tegnspenn er en <span> med sin egen innebygde CSS for vekt, holdning, farge og størrelse; og en hyperlenke er en <a href="..."> som pakker inn den synlige teksten sin. En enkelt linje kan inneholde flere spenn på rad, hver med forskjellig styling, og ett av dem kan være et anker. Stylingen er ikke dekorasjon som kan utelates. En klausul gjengitt i fet rød skrift fordi det er en juridisk advarsel, må forbli fet og rød etter flating, ellers vil det flatede dokumentet feilrepresentere originalen.
Så flatingmotoren kan ikke behandle blokken som én streng. Den må gå gjennom den innebygde strukturen, løse hver kjørings effektive stil bygger på å legge spennets innebygde CSS over tegneelementets basisfont, og legge ut kjøringene etter hverandre over linjen. HotPDF modellerer hvert av disse utlagte fragmentene som en intern TXFARichRun-post. Posten bærer kjøringens tekst, dens løste stil, dens målte boks, og, for et anker, Href-en den peker på.
Legge ut kjøringene fra venstre til høyre
Plassering er der formateringstekst slutter å være et parserproblem og blir et typografiproblem. Kjøringene deler en linje, så hver kjøring begynner der den forrige sluttet. Det er ingen markering som registrerer disse posisjonene; de må måles. Motorens interne LayoutRichText-rutine måler hver kjøring med de samme fontmetrikkene som senere skal tegne den, og setter deretter kjøringens horisontale forskyvning til den løpende summen av alle tidligere kjøringsbredder. Kjøring én starter ved tegneboksens origo, kjøring to starter ved bredden av kjøring én, kjøring tre ved den kombinerte bredden av de to første, og så videre over linjen.
Dette er grunnen til at måling av fontjustering betyr så mye. Layout-gjennomgangen måler fremrykk (advances); en separat gjengivelsesgjennomgang tegner glyfer. Hvis disse to gjennomgangene er uenige om fonten, vil boksene layouten beregnet ikke sitte under glyfene gjengiveren tegner. HotPDF holder dem i takt ved å avbilde hver kjørings løste stil på en fontspesifikasjon, via den interne RunStyleToFontSpec-hjelperen, som samsvarer med gjengiverens egne standardverdier for Arial på 10 punkter. Det målte fremrykket og den tegnede teksten stemmer da overens, og en kjørings beregnede boks dekker virkelig tegnene en leser 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;
Fra en ankerkjøring til en PDF Link-annotering
En hyperlenke i en ferdig PDF er ikke en del av sideinnholdet. Den er et eget objekt, en Link-annotering, beskrevet i ISO 32000-1 §12.5.6.5. Annoteringen har en /Rect som definerer det klikkbare rektangelet på siden og en handling som utløses når rektangelet klikkes. For en ekstern lenke er handlingen en URI-handling: /S /URI med måladressen som sin /URI-streng. Den synlige teksten under er vanlig sideinnhold; annoteringen er den usynlige aktive sonen lagt over den.
Flatingstien følger akkurat denne modellen. Når en kjøring bærer en Href, HotPDF først tegner den stylede teksten, og bygger deretter en Link-annotering over kjøringens boks. Det offentlige inngangspunktet for den annoteringen er sidemetoden AddURILink, som oppretter /Type /Annot /Subtype /Link-objektet med en /URI-handling og returnerer annoteringsordboken. Rektangelet er kjøringens målte boks, oversatt fra tegneelementets lokale koordinater til sidekoordinater. Resultatet er en lenke som lander nøyaktig på ankerteksten og ingen andre steder.
// 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;
Hvorfor treffboksen må komme fra målte bredder
Det er fristende å forestille seg å finne lenken ved å søke på siden etter dens synlige tekst og tegne rektangelet rundt det som blir funnet. Det fungerer ikke, og årsaken er fundamental for hvordan flatet tekst lagres. De stylede kjøringene tegnes med innebygde underfontsett (subset fonts). En underfont omnummererer glyfene den beholder, så sidens innholdsstrøm inneholder heksadesimale CID-koder, ikke de opprinnelige tegnkodene. Byte på siden er ikke bokstavene et menneske leser, og de er ikke søkbare som tekst. Et søk etter ankerets tittel finner ingenting, fordi den tittelen ikke eksisterer som bokstavelig tekst noen steder i strømmen.
Det eneste pålitelige ankeret for rektangelet er geometrien layout-gjennomgangen allerede produserte. Hver kjørings forskyvning og målte bredde ble beregnet mens linjen fløt, før noen glyf ble omnummerert, og de beskriver hvor teksten fysisk vil vises. HotPDF tar derfor lenkerektangelet direkte fra kjøringens lagt ned boks i stedet for fra et tekstoppslag. Fordi målingen brukte gjengivelsesfonten, er boksen korrekt uavhengig av subsetting. Geometri overlever kodingen; tekst gjør det ikke. Det er hele argumentet for posisjonering med målt bredde, og det er derfor en flating som prøver å ettermontere lenker ved tekstsøk produserer treffsoner som driver eller forsvinner.
Kjøre flatingen fra koden din
For en PDF som allerede inneholder en XFA-pakke, er inngangspunktet FlattenLoadedXFA. Last inn dokumentet, kall metoden, og lagre resultatet. Parameteren Editable bestemmer hva som skjer med skjemafelt: send True for å beholde dem som utfyllbare AcroForm-skjermelementer, eller False for å merke hvert skjermelement som skrivebeskyttet slik at utdataene blir en frossen oppføring. Tegneblokkene for formateringstekst, med deres stylede kjøringer og lenkeannoteringer, produseres uansett. Funksjonen returnerer antallet skjermelementer den sendte 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;
Les alltid XFAFlattenWarnings etter kallet. Listen tømmes ved starten av hver flating og samler opp en linje for hvert element motoren avslo å gjengi: en ustøttet felttype, et tegnebilde som ikke lot seg dekode, en exData-blokk uten brukbare spenn. Ingenting av dette utløser et unntak, så en tom advarselsliste er ditt bevis på at alt ble avbildet, og en ikke-tom liste forteller deg nøyaktig hvilke originaler du bør inspisere. Når du har rå XFA som XDP-byte i stedet for en lastet PDF, tar søskenmetoden ApplyXFAAsAcroForm disse bytene direkte og deler samme kodesøkevei og samme advarselsadferd. Den komplementære metoden AddXFAPacket går den andre veien, og bygger inn en XFA-pakke i et dokument du bygger.
Bekreftelse av resultatet i en leser, XFA-flating er dekket i vår gjennomgang om flating av XFA-skjemaer til AcroForm-skjermelementer. For den bredere historien om å bygge og plassere Link-annoteringer for hånd, utover de som flatingstien genererer, se arbeid med PDF-annoteringer i HotPDF. Begge bygger på samme annoterings- og skjemamodell som leveres med HotPDF Component for Delphi og C++Builder.