XFA (XML Forms Architecture) je zastaraný formát. ISO 32000-1 ho nesie v §12.7 s poznámkou, že z PDF 2.0 bol odstránený, a moderné prehliadače postupne odstraňujú svoje enginy pre XFA. Nič z toho však nevyprázdnilo archívy. Vládne vstupné formuláre, žiadosti o poistenie a bankové výpisy boli vytvárané ako XFA viac ako dve desaťročia a tieto súbory dodnes prichádzajú do e-mailových schránok a spracovateľských reťazcov. Keď prehliadač, ktorý ich predtým zobrazoval, prestane fungovať, formulár sa zmení na prázdnu stránku s textom "otvorte v inej čítačke". Trvalým riešením je sploštenie (flattening) XFA do statického obsahu PDF, ktorý dokáže vykresliť akákoľvek čítačka.
Ťažkou časťou tohto sploštenia nie sú polia. Textové polia a začiarkavacie políčka sa na widgety AcroForm mapujú pomerne jednoducho. Ťažkou časťou je rich text, ktorý XFA ukladá do kresliaceho prvku v bloku <exData contentType="text/html">. Tento blok je podmnožinou HTML s inline štýlmi a často aj odkazmi (anchors). Dostať ho na stránku znamená reprodukovať tak štylizovaný text, ako aj živé hyperodkazy, a hyperodkazy sú miestom, kde väčšina implementácií potichu rezignuje.
Ako v skutočnosti vyzerá rich text v XFA
Telo exData je malý kúsok XHTML. Odsek je <p>; štylizovaný rozsah znakov je <span> s vlastným inline CSS pre hrúbku, rez, farbu a veľkosť; a hyperodkaz je <a href="..."> obaľujúci svoj viditeľný text. Jeden riadok môže obsahovať niekoľko spanov za sebou, každý s iným štýlom, a jeden z nich môže byť odkazom. Štýlovanie nie je dekorácia, ktorú možno zahodiť. Klauzula vykreslená tučným červeným písmom, pretože ide o právne upozornenie, musí po sploštení zostať tučná a červená, inak by sploštený dokument skresľoval originál.
Splošťovací engine teda nemôže považovať blok za jeden reťazec. Musí prejsť inline štruktúru, vyriešiť efektívny štýl každého behu vrstvením inline CSS spanu nad základné písmo kresliaceho prvku a rozložiť behy jeden po druhom pozdĺž riadku. HotPDF modeluje každý z týchto rozložených fragmentov ako interný záznam TXFARichRun. Záznam nesie text behu, jeho vyriešený štýl, jeho nameraný rámec (box) a v prípade odkazu aj Href, na ktorý ukazuje.
Rozloženie behov zľava doprava
Umiestnenie je momentom, kedy rich text prestáva byť problémom analýzy a stáva sa problémom sadzby. Behy zdieľajú riadok, takže každý z nich začína tam, kde predchádzajúci skončil. Neexistuje žiadna značka, ktorá by tieto pozície zaznamenávala; musia sa zmerať. Interná rutina enginu LayoutRichText meria každý beh pomocou rovnakých metrík písma, ktoré ho neskôr vykreslia, a potom nastaví horizontálny posun behu na priebežný súčet šírok všetkých predchádzajúcich behov. Beh jeden začína na začiatku kresliaceho rámca, beh dva na šírke behu jeden, beh tri na kombinovanej šírke prvých dvoch atď. pozdĺž riadku.
Preto na zarovnaní písma pri meraní tak veľmi záleží. Prechod rozloženia meria pokroky (advances); samostatný prechod vykresľovania kreslí glyfy. Ak sa tieto dva prechody nezhodnú na písme, rámce, ktoré rozloženie vypočítalo, nebudú sedieť pod glyfmi, ktoré vykresľovač maľuje. HotPDF keeps them in step by mapping each run's resolved style onto a font specification, through the internal RunStyleToFontSpec helper, that matches the renderer's own defaults of Arial at 10 points. The measured advance and the drawn text then agree, and a run's computed box genuinely covers the characters a reader sees.
// 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;
Od odkazového behu k PDF anotácii odkazu (Link annotation)
Hyperodkaz v hotovom PDF nie je súčasťou obsahu stránky. Je to samostatný objekt, anotácia odkazu (Link annotation), opísaná v ISO 32000-1 §12.5.6.5. Anotácia má položku /Rect, ktorá definuje klikateľný obdĺžnik na stránke, a akciu, ktorá sa spustí po kliknutí na obdĺžnik. Pre externý odkaz je akciou akcia URI: /S /URI s cieľovou adresou ako reťazcom /URI. Viditeľný text pod ním je bežným obsahom stránky; anotácia je neviditeľná aktívna zóna položená nad ním.
Cesta sploštenia presne sleduje tento model. Keď beh nesie Href, HotPDF najprv nakreslí štylizovaný text a potom vytvorí anotáciu odkazu nad rámcom behu. Verejným vstupným bodom pre túto anotáciu je metóda stránky AddURILink, ktorá vytvorí objekt /Type /Annot /Subtype /Link s akciou /URI a vráti slovník anotácie. Jej obdĺžnik je nameraným rámcom behu, preloženým z lokálnych súradníc kresliaceho prvku do súradníc stránky. Výsledkom je odkaz, ktorý pristane presne na texte odkazu a nikde inde.
// 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;
Prečo musí aktívna zóna pochádzať z nameraných šírok
Je lákavé predstaviť si lokalizáciu odkazu vyhľadaním jeho viditeľného textu na stránke a nakreslením obdĺžnika okolo čohokoľvek, čo sa nájde. To však nefunguje a dôvod je zásadný pre spôsob, akým sa sploštený text ukladá. Štylizované behy sú vykresľované pomocou vložených písiem podmnožiny (subset fonts). Písmo podmnožiny prečísluje glyfy, ktoré si ponecháva, takže tok obsahu stránky obsahuje hexadecimálne kódy CID, nie pôvodné kódy znakov. Bajty na stránke nie sú písmenami, ktoré človek číta, a nedá sa v nich vyhľadávať ako v texte. Hľadanie popisu odkazu nenájde nič, pretože tento popis neexistuje ako doslovný text nikde v toku.
Jediným spoľahlivým záchytným bodom pre obdĺžnik je geometria, ktorú už prechod rozloženia vygeneroval. Posun každého behu a nameraná šírka boli vypočítané počas toku riadku, pred prečíslovaním akéhokoľvek glyfu, a popisujú, kde sa text fyzicky objaví. HotPDF preto berie obdĺžnik odkazu priamo z položeného rámca behu, a nie z akéhokoľvek vyhľadávania textu. Keďže meranie použilo vykresľovacie písmo, rámec je správny bez ohľadu na subsetting. Geometria prežije kódovanie; text nie. To je hlavný argument pre určovanie polohy podľa nameranej šírky a dôvod, prečo splošťovač, ktorý sa pokúša spätne doplniť odkazy pomocou vyhľadávania textu, vytvára aktívne zóny, ktoré sa posúvajú alebo miznú.
Spustenie sploštenia z vášho kódu
Pre PDF, ktoré už obsahuje balík XFA, je vstupným bodom FlattenLoadedXFA. Načítajte dokument, zavolajte metódu a uložte výsledok. Parameter Editable rozhoduje o tom, čo sa stane s poliami formulára: odovzdajte True, aby zostali ako vyplniteľné widgety AcroForm, alebo False, aby bol každý widget označený len na čítanie, takže výstupom bude zmrazený záznam. Rich-text kresliace bloky so svojimi štylizovanými behmi a anotáciami odkazov sa vytvoria v oboch prípadoch. Funkcia vracia počet emitovaných widgetov.
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;
Po volaní si vždy prečítajte XFAFlattenWarnings. Zoznam sa vymaže na začiatku každého sploštenia a akumuluje riadok pre každý prvok, ktorý engine odmietol vykresliť: nepodporovaný druh poľa, obrázok, ktorý sa nedal dekódovať, blok exData bez použiteľných spanov. Žiadne z nich nevyvolá výnimku, takže prázdny zoznam varovaní je vaším dôkazom, že všetko sa namapovalo, a nenulový vám presne povie, ktoré originály máte skontrolovať. Ak držíte surové XFA ako bajty XDP namiesto načítaného PDF, súrodenecká metóda ApplyXFAAsAcroForm takto bajty priamo a zdieľa rovnakú cestu kódu a rovnaké správanie varovaní. Doplnková metóda AddXFAPacket ide opačným smerom, pričom vkladá balík XFA do dokumentu, ktorý vytvárate.
Potvrdenie výsledku v čítačke
Otvorte sploštený súbor v programe Acrobat alebo v akomkoľvek aktuálnom prehliadači a skontrolujte dve veci. Po prvé, že rich text sa vykreslil so zachovaním jeho štýlovania: tučné behy sú tučné, farebné behy nesú svoju farbu a spany sedia v správnom poradí na riadku, namiesto toho, aby sa prekrývali alebo vybiehali z rámca. Po druhé, hyperodkazy sú aktívne. Prejdite myšou nad odkaz a stavový riadok by mal zobraziť cieľovú adresu; kliknite naň a akcia URI by ju mala otvoriť. Pomocou inšpektora anotácií v prehliadači potvrďte, že každá z nich je skutočnou anotáciou /Link, ktorej /Rect tesne obopína text odkazu a sedí nad obsahom, ktorý je teraz obyčajným vykresleným glyfom, a nie formulárom riadeným XFA. Táto kombinácia, štylizovaný statický text plus skutočné anotácie Link na správnych obdĺžnikoch, je tým, vďaka čomu sploštený dokument prežije XFA enginy, ktoré už nepotrebuje.
Sploštenie samotných polí, textových polí, začiarkavacích políčok a zoznamov možností, ktoré obklopujú tento rich text, je popísané v našom sprievodcovi sploštením formulárov XFA do widgetov AcroForm. Pre širší príbeh o ručnom vytváraní a umiestňovaní anotácií odkazov, mimo tých, ktoré generuje cesta sploštenia, si pozrite prácu s PDF anotáciami v HotPDF. Obe stavajú na rovnakom modeli anotácií a formulárov, ktorý sa dodáva s komponentom HotPDF Component pre Delphi and C++Builder.