Technický článek

Sloučení (Flattening) XFA Rich-Text odkazů na PDF odkazy v Delphi

XFA neboli XML Forms Architecture je zastaralá. ISO 32000-1 ji uvádí v § 12.7 s poznámkou, že byla z PDF 2.0 odstraněna, a moderní prohlížeče postupně vyřazují své moduly XFA. To však archivy nevyprázdnilo. Formuláře pro státní agendu, žádosti o pojištění a bankovní výpisy se po větší část dvou desetiletí vytvářely jako XFA a tyto soubory dodnes přicházejí do e-mailových schránek a zpracovatelských kanálů dokumentů. Když je prohlížeč, který je dříve vykresloval, přestane podporovat, formulář se změní na prázdnou stránku se zástupným textem „otevřete prosím v jiné čtečce“. Trvalým řešením je zploštit XFA do statického obsahu PDF, který vykreslí každá čtečka

Nejtěžší částí tohoto zploštění nejsou pole. Textová pole a zaškrtávací políčka se na widgety AcroForm převádějí poměrně přímočaře. Obtížný je bohatý text, který XFA ukládá uvnitř prvku draw v bloku <exData contentType="text/html">. Jde o podmnožinu HTML s vloženým stylováním a často i kotvami. Jeho přenesení na stránku vyžaduje reprodukovat jak stylovaný text, tak funkční hypertextové odkazy, a právě u odkazů se většina implementací nenápadně vzdá

Jak ve skutečnosti vypadá bohatý text XFA

Tělo exData je malý výsek XHTML. Odstavec je <p>; stylovaný úsek znaků je <span> s vlastním vloženým CSS pro tloušťku písma, sklon, barvu a velikost; hypertextový odkaz je <a href="...">, který obaluje viditelný text. Jeden řádek může obsahovat několik úseků za sebou, každý s jiným stylem, a jeden z nich může být kotvou. Stylování není dekorace, kterou lze vypustit. Ustanovení vykreslené tučně červeně jako právní upozornění musí po zploštění zůstat tučné a červené, jinak zploštěný dokument zkresluje originál

Modul pro zploštění proto nemůže s blokem zacházet jako s jediným řetězcem. Musí projít vloženou strukturu, určit výsledný styl každého úseku překrytím vloženého CSS prvku span nad základním písmem prvku draw a vysázet úseky postupně přes řádek. HotPDF každý takto vysázený fragment modeluje interním záznamem TXFARichRun. Záznam obsahuje text úseku, jeho vyřešený styl, změřený rámeček a u kotvy také Href, na který ukazuje

Rozmístění úseků zleva doprava

U bohatého textu už určování polohy není jen problémem analýzy, ale stává se problémem sazby. Úseky sdílejí řádek, takže každý začíná tam, kde skončil předchozí. Žádná značka tyto polohy nezaznamenává; musí se změřit. Interní rutina modulu LayoutRichText změří každý úsek pomocí stejných metrik písma, jaké jej později vykreslí, a potom nastaví vodorovný posun úseku na průběžný součet šířek všech předchozích úseků. První úsek začíná v počátku rámečku draw, druhý na šířce prvního, třetí na součtu šířek prvních dvou a tak dále přes řádek

Proto je zarovnání písem při měření tak důležité. Průchod rozvržením měří posuny znaků, zatímco samostatný vykreslovací průchod kreslí glyfy. Pokud se tyto dva průchody neshodnou na písmu, rámečky vypočtené rozvržením nebudou ležet pod glyfy, které vykreslovač nakreslí. HotPDF je drží ve shodě mapováním vyřešeného stylu každého úseku na specifikaci písma prostřednictvím interního pomocníka RunStyleToFontSpec, který odpovídá vlastním výchozím hodnotám vykreslovače Arial o velikosti 10 bodů. Změřený posun a nakreslený text se pak shodují a vypočtený rámeček úseku skutečně pokrývá znaky, které čtenář vidí

Diagram HotPDF sláčející blok rich-text XFA exData v Delphi do stylizovaných runs vyložených zleva doprava se změřenými šířkami, kde ukotvený run si drží href a každý fragment se stane záznamem TXFARichRun
Flatten engine projde inline strukturu exData, rozřeší styl každého úseku a měří šířky vykreslovacím písmem, takže rozložené boxy sedí přesně pod namalovanými glyfy
// Pojmový tvar jednoho vysázeného úseku. Modul vytváří pole těchto
// záznamů interně; sami je nikdy nesestavujete, ale pole vysvětlují, jak je
// rámeček odkazu odvozen z měřené geometrie, nikoli z textu.
type
  TRichRunInfo = record
    Dx, Dy : Double;       // vlevo nahoře, vzhledem k počátku rámečku draw
    W, H   : Double;       // změřený rámeček úseku (šířka z průchodu rozvržením)
    Text   : AnsiString;   // viditelné znaky úseku
    Href   : AnsiString;   // URI cíl pro úsek <a>, jinak ''
  end;

Od úseku kotvy k anotaci PDF Link

Hypertextový odkaz v hotovém PDF není součástí obsahu stránky. Je to samostatný objekt, anotace Link, popsaná v ISO 32000-1 § 12.5.6.5. Anotace má /Rect, který určuje klikací obdélník na stránce, a akci, jež se spustí po kliknutí na obdélník. U externího odkazu jde o akci URI: /S /URI s cílovou adresou v řetězci /URI. Viditelný text pod ní je obyčejný obsah stránky; anotace je neviditelná aktivní zóna položená přes něj

Cesta zploštění přesně následuje tento model. Když úsek nese Href, HotPDF nejdříve vykreslí stylovaný text a pak nad rámečkem úseku vytvoří anotaci Link. 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í slovník anotace. Její obdélník je změřený rámeček úseku převedený z místních souřadnic prvku draw do souřadnic stránky. Výsledkem je odkaz, který přesně leží na textu kotvy a nikde jinde

Diagram cesty slácení HotPDF převádějící změřený draw-local box ukotveného run do souřadnic strany a vydávající anotaci Subtype Link s akcí URI, jejíž Rect objímá ukotvený text
Úsek kotvy se stane namalovaným textem plus anotací Link, jejíž /Rect je změřený box úseku přeložený do souřadnic stránky, s akcí URI vytvořenou AddURILink
// Stejné veřejné API, které cesta zploštění používá pro každý úsek kotvy. Vytváří
// anotaci Link dle ISO 32000-1 12.5.6.5: /Subtype /Link s akcí /URI
// nad zadaným obdélníkem. Volitelný popis vyplní /Contents tak, aby
// čtečka obrazovky mohla ohlásit cíl.
var
  LinkRect: TRect;
  Annot: THPDFDictionaryObject;
begin
  LinkRect := Rect(72, 690, 268, 706);  // rámeček pro klik v prostoru stránky pro úsek
  Annot := Pdf.CurrentPage.AddURILink(LinkRect,
    'https://www.example.gov/appeal', 'File an appeal online');
end;

Proč musí aktivní rámeček vycházet ze změřených šířek

Je lákavé představit si, že odkaz najdete vyhledáním jeho viditelného textu na stránce a obdélník nakreslíte kolem nalezeného výsledku. To nefunguje a důvod vyplývá ze způsobu, jakým je zploštěný text uložen. Stylované úseky se kreslí vloženými podmnožinovými písmy. Podmnožinové písmo přečísluje glyfy, které obsahuje, takže obsahový proud stránky drží šestnáctkové kódy CID, ne původní kódy znaků. Bajty na stránce nejsou písmena, která čte člověk, a nelze je jako text vyhledávat. Hledání popisku kotvy nic nenajde, protože tento popisek nikde v proudu neexistuje jako doslovný text

Jediným spolehlivým vodítkem pro obdélník je geometrie, kterou už vytvořil průchod rozvržením. Posun a změřená šířka každého úseku byly vypočteny při toku řádku ještě před přečíslováním glyfů a popisují, kde se text fyzicky objeví. HotPDF proto bere obdélník odkazu přímo z vysázeného rámečku úseku, nikoli z vyhledávání textu. Protože měření použilo vykreslovací písmo, je rámeček správný bez ohledu na vytváření podmnožin. Geometrie kódování přežije, text nikoli. To je celý důvod pro určování polohy podle změřené šířky a také důvod, proč zplošťovač, který se pokouší dodatečně vytvářet odkazy pomocí vyhledávání textu, vytváří aktivní zóny, které se posunou nebo zmizí

Diagram ukazující, proč HotPDF bere hit boxy odkazů XFA ze změřené geometrie run: podmnožinové fonty přečíslují glyfy na kódy CID, takže hledání textu nic nenajde, zatímco offsety a šířky z layout průchodu přežijí a dají správný Rect
Vložená subset písma přečíslují glyfy na CID kódy, takže hledání textu nenajde nic; změřené offsety a šířky z průchodu rozvržením jsou jediné kotvy, jež kódování přežijí

Řízení zploštění z vlastního kódu

U PDF, které už obsahuje paket 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ředejte True, chcete-li je ponechat jako vyplnitelné widgety AcroForm, nebo False, chcete-li každý widget označit jen pro čtení, aby výstup tvořil neměnný záznam. Bloky draw s bohatým textem, jejich stylovanými úseky a anotacemi odkazů vzniknou v obou případech. Funkce vrací počet widgetů, které vytvořila

var
  Pdf: THotPDF;
  Emitted, i: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    Pdf.LoadFromFile('xfa_appeal_form.pdf');
    // True ponechává pole vyplnitelná; False je zamrazí jen pro čtení.
    Emitted := Pdf.FlattenLoadedXFA(True);

    // Cokoli, co se modulu nepodařilo namapovat, je hlášeno, nikoli vyhozeno.
    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 přečtěte XFAFlattenWarnings. Seznam se na začátku každého zploštění vymaže a shromažďuje řádek pro každý prvek, který modul odmítl vykreslit: nepodporovaný typ pole, obrázek draw, který se nepodařilo dekódovat, nebo blok exData bez použitelných úseků. Žádný z nich nevyvolá výjimku, takže prázdný seznam varování dokládá, že se vše namapovalo, a neprázdný vám přesně řekne, které originály prověřit. Máte-li nezpracované XFA jako bajty XDP namísto načteného PDF, sourozenecká metoda ApplyXFAAsAcroForm přijímá tyto bajty přímo a používá stejnou cestu kódu i stejné chování varování. Doplňková metoda AddXFAPacket provádí opačnou činnost a vloží paket XFA do dokumentu, který vytváříte

Ověření výsledku ve čtečce

Otevřete zploštěný soubor v Acrobatu nebo v libovolném aktuálním prohlížeči a zkontrolujte dvě věci. Zaprvé, zda se bohatý text vykreslil s neporušeným stylováním: tučné úseky jsou tučné, barevné úseky si zachovaly barvu a spany jsou na řádku ve správném pořadí, místo aby se překrývaly nebo vybíhaly z rámečku. Zadruhé, zda jsou hypertextové odkazy funkční. Po najetí na kotvu by měl stavový řádek ukázat cílovou adresu; po kliknutí by se měla otevřít akce URI. Pomocí inspektoru anotací prohlížeče ověřte, že každý z nich je skutečná anotace /Link, jejíž /Rect těsně obepíná text kotvy a leží nad obsahem, který nyní tvoří obyčejně vykreslené glyfy místo formulářem vykreslovaného XFA. Právě tato kombinace, stylovaný statický text a skutečné anotace Link ve správných obdélnících, umožní zploštěnému dokumentu přežít moduly XFA, které už nepotřebuje

Zploštění samotných polí — textových polí, zaškrtávacích políček a seznamů voleb obklopujících tento bohatý text — popisuje náš návod na zploštění formulářů XFA do widgetů AcroForm. Širší souvislosti ručního vytváření a umisťování anotací Link nad rámec těch, které vytváří cesta zploštění, najdete v článku práce s anotacemi PDF v HotPDF. Oba články vycházejí ze stejného modelu anotací a formulářů, který poskytuje HotPDF Delphi Component pro Delphi a C++Builder