Architektura XFA (XML Forms Architecture) je zastaralá. ISO 32000-1 ji uvádí v §12.7 s poznámkou, že z verze PDF 2.0 byla odstraněna, a moderní prohlížeče postupně ukončují podporu svých XFA enginů. Nic z toho však nevyprázdnilo archivy. Vládní formuláře, žádosti o pojištění a bankovní výpisy byly vytvářeny jako XFA po více než dvě desetiletí a tyto soubory dodnes přicházejí do e-mailových schránek a dokumentových pipelines. Když je prohlížeč, který je dříve vykresloval, přestane podporovat, formulář se změní na prázdnou stránku s textem "otevřete v jiném programu". Trvalým řešením je zploštění XFA do statického obsahu PDF, který dokáže vykreslit jakákoli čtečka.
Těžkou částí zploštění nejsou samotná pole. Textová pole a zaškrtávací políčka se na prvky AcroForm mapují celkem čistě. Složitou částí je formátovaný text (rich text), který XFA ukládá uvnitř vykreslovacího prvku (draw element) v bloku <exData contentType="text/html">. Tento blok je podmnožinou HTML s inline styly a často i odkazy (anchors). Jeho přenesení na stránku vyžaduje reprodukci stylovaného textu i živých odkazů, a právě u odkazů většina implementací potichu vzdává snahu.
Jak formátovaný text XFA ve skutečnosti vypadá
Tělo exData je malým fragmentem XHTML. Odstavec je <p>; stylovaná skupina znaků je <span> s vlastním inline CSS pro řez, sklon, barvu a velikost; a odkaz je <a href="..."> obalující svůj viditelný text. Jediný řádek může obsahovat několik spanů za sebou, z nichž každý má jiné stylování a jeden z nich může být odkazem. Stylování není dekorace, kterou lze zahodit. Ustanovení vykreslené tučným červeným písmem, protože se jedná o právní varování, musí zůstat tučné a červené i po zploštění, jinak by zploštělý dokument zkresloval originál.
Zplošťovací engine tedy nemůže k bloku přistupovat jako k jednomu řetězci. Musí projít inline strukturu, vyhodnotit výsledný styl každého bloku vrstvením inline CSS prvků span nad výchozím písmem vykreslovacího prvku a poskládat bloky jeden za druhým na řádek. HotPDF modeluje každý z těchto rozložených fragmentů jako interní záznam TXFARichRun. Záznam nese text bloku, jeho vyhodnocený styl, jeho změřený box a pro odkaz také Href, na který ukazuje.
Rozvržení bloků zleva doprava
Umísťování je fází, kde formátovaný text přestává být problémem analýzy (parsing) a stává se problémem sazby (typesetting). Bloky sdílejí řádek, takže každý blok začíná tam, kde předchozí skončil. Neexistuje žádný kód, který by tyto pozice zaznamenával; musí se změřit. Interní rutina enginu LayoutRichText změří každý blok se stejnými metrikami písma, které jej později vykreslí, a poté nastaví horizontální posun bloku na průběžný součet šířek všech předchozích bloků. První blok začíná na počátku vykreslovacího pole, druhý začíná na šířce prvního, třetí na kombinované šířce prvních dvou atd. podél řádku.
To je důvod, proč na zarovnání měřicího písma tolik záleží. Krok rozvržení měří posuny (advances); samostatný krok vykreslení kreslí glyfy. Pokud se tyto dva kroky neshodnou na písmu, boxy vypočítané při rozvržení nebudou sedět pod glyfy, které vykreslovací modul maluje. HotPDF je udržuje v souladu tím, že mapuje vyhodnocený styl každého bloku na specifikaci písma (pomocí interního pomocníka RunStyleToFontSpec), která odpovídá vlastním výchozím hodnotám vykreslovacího modulu, což je Arial o velikosti 10 bodů. Změřený posun a vykreslený text pak souhlasí a vypočítaný box bloku skutečně pokrývá znaky, které čtenář vidí.
// 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 bloku odkazu k PDF anotaci odkazu
Odkaz v hotovém PDF není součástí obsahu stránky. Jedná se o samostatný objekt, anotaci odkazu (Link annotation), popsanou v ISO 32000-1 §12.5.6.5. Anotace má položku /Rect, která definuje klikatelný obdélník na stránce, a akci, která se spustí při kliknutí na tento obdélník. Pro externí odkaz je akcí akce URI: /S /URI s cílovou adresou jako jejím řetězcem /URI. Viditelný text pod ním je běžným obsahem stránky; anotace je neviditelná aktivní zóna položená nad ním.
Cesta zploštění sleduje přesně tento model. Když blok nese Href, HotPDF nejprve vykreslí stylovaný text a poté nad boxem bloku sestaví anotaci odkazu. Veřejným vstupním bodem pro tuto anotaci je metoda stránky AddURILink, která vytvoří objekt /Type /Annot /Subtype /Link s akcí /URI a vrátí adresář anotace. Jejím obdélníkem je změřený box bloku, převedený z lokálních souřadnic vykreslovacího prvku do souřadnic stránky. Výsledkem je odkaz, který sedí přesně na textu odkazu a nikde jinde.
// 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;
Proč klikací plocha musí pocházet ze změřených šířek
Je lákavé představit si vyhledání odkazu vyhledáním jeho viditelného textu na stránce a vykreslením obdélníku kolem nalezeného obsahu. To však nefunguje a důvod je zásadní pro způsob, jakým je zploštělý text ukládán. Stylované bloky jsou vykreslovány pomocí vložených podmnožin písem (subset fonts). Podmnožina písma přečísluje glyfy, které si ponechá, takže proud obsahu stránky obsahuje hexadecimální kódy CID, nikoli původní kódy znaků. Bajty na stránce nejsou písmena, která člověk čte, a nelze v nich vyhledávat jako v textu. Vyhledání popisku odkazu nic nenajde, protože tento popisek neexistuje jako doslovný text nikde v proudu.
Jediným spolehlivým ukotvením pro obdélník je geometrie, kterou již vygeneroval krok rozvržení. Posun a změřená šířka každého bloku byly vypočítány při toku řádku, před jakýmkoli přečíslováním glyfů, a popisují, kde se text fyzicky objeví. HotPDF proto přebírá obdélník odkazu přímo z umístěného boxu bloku, nikoli z jakéhokoli vyhledávání textu. Vzhledem k tomu, že měření použilo vykreslované písmo, box je správný bez ohledu na vytváření podmnožin. Geometrie kódování přežije; text nikoli. To je hlavní argument pro umísťování podle změřené šířky a důvod, proč zplošťovací nástroje, které se snaží doplňovat odkazy zpětně pomocí vyhledávání textu, vytvářejí aktivní zóny, které se posouvají nebo mizí.
Řízení zploštění z vašeho kódu
Pro PDF, které již obsahuje balíček XFA, je vstupním bodem FlattenLoadedXFA. Načtěte dokument, zavolejte metodu a výsledek uložte. Parametr Editable určuje, co se stane s poli formuláře: předáním hodnoty True je ponecháte jako vyplnitelné prvky AcroForm, předáním False označíte každý prvek jako pouze pro čtení, takže výstupem bude zmrazený záznam. Bloky pro vykreslování formátovaného textu se svými stylovanými bloky a anotacemi odkazů se vygenerují v obou případech. Funkce vrací počet prvků, které vygenerovala.
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 volání vždy čtěte XFAFlattenWarnings. Seznam se vymaže na začátku každého zploštění a shromažďuje záznamy o každém prvku, který engine odmítl vykreslit: nepodporovaný druh pole, obrázek, který nešlo dekódovat, nebo blok exData bez použitelných spanů. Žádný z nich nevyvolá výjimku, takže prázdný seznam varování je vaším důkazem, že se vše namapovalo, a nenulový vám přesně řekne, které originály máte zkontrolovat. Pokud držíte surová data XFA jako bajty XDP namísto načteného PDF, sesterská metoda ApplyXFAAsAcroForm přebírá tyto bajty přímo a sdílí stejnou cestu kódu i stejné chování při varování. Doplňková metoda AddXFAPacket jde opačným směrem a vkládá balíček XFA do dokumentu, který právě vytváříte.
Ověření výsledku ve čtečce
Otevřete zploštělý soubor v programu Acrobat nebo v jakémkoli jiném aktuálním prohlížeči a zkontrolujte dvě věci. Za prvé, zda se formátovaný text vykreslil se zachováním stylu: tučné části jsou tučné, barevné části mají svou barvu a spany jsou ve správném pořadí na řádku, namísto aby se překrývaly nebo utíkaly z pole. Za druhé, zda jsou odkazy aktivní. Najeďte na odkaz a ve stavovém řádku by se měla zobrazit cílová adresa; klikněte na něj a akce URI by jej měla otevřít. Pomocí inspektoru anotací v prohlížeči ověřte, že každý odkaz je skutečnou anotací /Link, jejíž /Rect obepíná text odkazu a leží nad obsahem, který je nyní tvořen prostými vykreslenými glyfy, nikoli formulářem generovaným XFA. Tato kombinace, tedy stylovaný statický text a skutečné anotace odkazů na správných obdélnících, je tím, co umožňuje zploštělému dokumentu přežít XFA enginy, které již nepotřebuje.
Zploštění samotných polí (textových polí, zaškrtávacích políček a výběrových seznamů), která tento formátovaný text obklopují, je popsána v našem průvodci zploštěním formulářů XFA do prvků AcroForm. Pro širší přehled o ručním vytváření a umísťování anotací odkazů (mimo ty, které generuje cesta zploštění) viz práce s PDF anotacemi v HotPDF. Obojí staví na stejném modelu anotací a formulářů, který je dodáván s produktem HotPDF Component pro Delphi and C++Builder.