Technical Article

Sploščevanje hiperpovezav bogatega besedila XFA v povezave PDF v Delphi

XFA (XML Forms Architecture) je zastarela. Standard ISO 32000-1 jo vsebuje v §12.7 z opombo, da je odstranjena iz PDF 2.0, sodobni pregledovalniki pa drug za drugim opuščajo svoje pogone XFA. Nič od tega pa ni spraznilo arhivov. Vladni obrazci za vnos, zavarovalniške vloge in bančni izpiski so bili večji del dveh desetletij ustvarjeni kot XFA in te datoteke še danes prihajajo v nabiralnike in dokumentne cevovode. Ko pregledovalnik, ki jih je včasih izrisoval, to preneha početi, se obrazec spremeni v prazno stran z nadomestnim besedilom "odprite v drugem bralniku". Trajna rešitev je sploščitev XFA v statično vsebino PDF, ki jo lahko izriše kateri koli bralnik.

Težak del te sploščitve niso polja. Besedilna polja in potrditvena polja se precej enostavno preslikajo v gradnike AcroForm. Težak del je bogato besedilo, ki ga XFA shranjuje znotraj risalnega elementa, v bloku <exData contentType="text/html">. Ta blok je podnabor HTML z vdelanim oblikovanjem in pogosto tudi sidri (povezavami). Prenos tega na stran pomeni reprodukcijo tako oblikovanega besedila kot tudi aktivnih hiperpovezav, in hiperpovezave so mesto, kjer večina implementacij tiho odstopi.

Kako je bogato besedilo XFA dejansko videti

Telo exData je majhen delček XHTML. Odstavek je <p>; oblikovan razpon znakov je <span> z lastnim vdelanim CSS-om za težo, slog, barvo in velikost; hiperpovezava pa je <a href="...">, ki ovija svoje vidno besedilo. Ena vrstica lahko vsebuje več zaporednih razponov, vsakega z drugačnim oblikovanjem, eden od njih pa je lahko sidro. Oblikovanje ni dekoracija, ki bi jo lahko izpustili. Določba, izrisana s krepko rdečo barvo, ker gre za pravno opozorilo, mora po sploščitvi ostati krepka in rdeča, sicer sploščeni dokument napačno predstavlja izvirnik.

Zato pogon za sploščevanje bloka ne more obravnavati kot en niz. Prehoditi mora vdelano strukturo, razrešiti dejanski slog vsakega teka (run) z nalaganjem vdelanega CSS razpona čez osnovno pisavo risalnega elementa ter razporediti teke enega za drugim čez vrstico. HotPDF modelira vsakega od teh razporejenih fragmentov kot notranji zapis TXFARichRun. Zapis nosi besedilo teka, njegov razrešeni slog, njegovo izmerjeno polje in, v primeru sidra, Href, na katerega kaže.

Razporejanje tekov od leve proti desni

Pozicioniranje je točka, kjer bogato besedilo preneha biti težava razčlenjevanja in postane težava stavljenja. Teki si delijo vrstico, zato se vsak tek začne tam, kjer se je prejšnji končal. Ni označevanja, ki bi beležilo te položaje; treba jih je izmeriti. Notranja rutina pogona LayoutRichText izmeri vsak tek z enakimi metričnimi podatki pisave, ki jo bo kasneje poslikala, nato pa nastavi vodoravni odmik teka na tekočo vsoto vseh širin predhodnih tekov. Prvi tek se začne na izhodišču risalnega polja, drugi tek se začne pri širini prvega teka, tretji pri skupni širini prvih dveh in tako naprej čez vrstico.

Zato je poravnava pisave pri meritvah tako zelo pomembna. Korak postavitve meri premike; ločen korak izrisa pa riše glife. Če se ta dva koraka ne ujmeta glede pisave, polja, ki jih je izračunala postavitev, ne bodo ležala pod glifi, ki jih izrisovalnik riše. HotPDF jih ohranja usklajene tako, da preslika razrešeni slog vsakega teka v specifikacijo pisave prek notranjega pomočnika RunStyleToFontSpec, ki ustreza lastnim privzetim nastavitvam izrisovalnika (Arial pri 10 točkah). Izmerjeni premik in izrisano besedilo se nato ujemata, izračunano polje teka pa dejansko pokriva znake, ki jih bralec vidi.

// 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 sidrnega teka do pripombe povezave PDF

Hiperpovezava v končnem PDF-ju ni del vsebine strani. Gre za ločen objekt, pripombo povezave (Link annotation), opisan v standardu ISO 32000-1 §12.5.6.5. Pripomba ima parameter /Rect, ki določa klikljivi pravokotnik na strani, in dejanje, ki se sproži ob kliku na pravokotnik. Za zunanjo povezavo je to dejanje URI: /S /URI s ciljnim naslovom kot nizom /URI. Vidno besedilo pod njim je običajna vsebina strani; pripomba pa je nevidno aktivno območje, položeno čez njo.

Pot sploščevanja sledi natanko temu modelu. Ko tek vsebuje Href, HotPDF najprej izriše oblikovano besedilo, nato pa zgradi pripombo povezave čez polje teka. Javna vstopna točka za to pripombo je metoda strani AddURILink, ki ustvari objekt /Type /Annot /Subtype /Link z dejanjem /URI in vrne slovar pripombe. Njen pravokotnik je izmerjeno polje teka, prevedeno iz lokalnih koordinat risalnega elementa v koordinate strani. Rezultat je povezava, ki pristane natanko na sidrnem besedilu in nikjer drugje.

// 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;

Zakaj mora aktivno območje izhajati iz izmerjenih širin

Misel na to, da bi povezavo locirali z iskanjem njenega vidnega besedila na strani in risanjem pravokotnika okoli najdenega, je sicer privlačna. Vendar to ne deluje, razlog pa je temeljne narave v tem, kako je shranjeno sploščeno besedilo. Oblikovani teki so izrisani z vdelanimi podnabori pisav. Podnabor pisave ponovno oštevilči glife, ki jih obdrži, zato tok vsebine strani vsebuje šestnajstiške kode CID in ne prvotnih kod znakov. Bajti na strani niso črke, ki jih bere človek, in po njih ni mogoče brskati kot po besedilu. Iskanje naslova sidra ne najde ničesar, saj ta naslov nikjer v toku ne obstaja kot dobesedno besedilo.

Edino zanesljivo sidro za pravokotnik je geometrija, ki jo je že ustvaril korak postavitve. Odmik vsakega teka in izmerjena širina sta bila izračunana med tekom vrstice, pred ponovnim številčenjem glifov, in opisujeta, kje se bo besedilo fizično pojavilo. HotPDF zato vzame pravokotnik povezave neposredno iz postavljenega polja teka in ne iz kakršnega koli iskanja besedila. Ker je meritev uporabila pisavo za izris, je polje pravilno ne glede na delitev pisav. Geometrija preživi kodiranje; besedilo pa ne. To je celoten argument za pozicioniranje na podlagi izmerjene širine in razlog, zakaj program za sploščevanje, ki poskuša naknadno dodati povezave z iskanjem besedila, ustvari aktivna območja, ki se zamaknejo ali izginejo.

Upravljanje sploščevanja iz vaše kode

Za PDF, ki že vsebuje paket XFA, je vstopna točka FlattenLoadedXFA. Naložite dokument, pokličete metodo in shranite rezultat. Parameter Editable določa, kaj se zgodi z obrazčnimi polji: posredujte True, da jih ohranite kot izpolnljive gradnike AcroForm, ali False, da vsak gradnik označite kot samo za branje, tako da je izhod zamrznjen zapis. Bloki za risanje bogatega besedila z njihovimi oblikovanimi teki in pripombami povezav se ustvarijo v obeh primerih. Funkcija vrne število gradnikov, ki jih je oddala.

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 klicu vedno preberite seznam XFAFlattenWarnings. Seznam se počisti na začetku vsake sploščitve in kopiči vrstice za vsak element, ki ga pogon ni želil izrisati: nepodprta vrsta polja, risalna slika, ki se ni dekodirala, ali blok exData brez uporabnih razponov. Nobena od teh situacij ne sproži izjeme, zato je prazen seznam opozoril vaš dokaz, da se je vse uspešno preslikalo, neprazen pa vam natančno pove, katere izvirnike morate pregledati. Ko imate surovi XFA kot bajte XDP in ne kot naložen PDF, sorodna metoda ApplyXFAAsAcroForm neposredno sprejme te bajte ter si deli enako pot kode in obnašanje opozoril. Dopolnilna metoda AddXFAPacket deluje v nasprotni smeri in vgradi paket XFA v dokument, ki ga gradite.

Potrditev rezultata v bralniku

Odprite sploščeno datoteko v programu Acrobat ali katerem koli trenutnem pregledovalniku in preverite dve stvari. Prvič, bogato besedilo se mora izrisati z nedotaknjenim oblikovanjem: krepki teki so krepki, barvni teki nosijo svojo barvo, razponi pa se nahajajo v pravilnem vrstnem redu v vrstici, namesto da bi se prekrivali ali tekli čez rob polja. Drugič, hiperpovezave so aktivne. Pomaknite se nad sidro in vrstica stanja bi morala prikazati ciljni naslov; kliknite nanj in dejanje URI bi ga moralo odpreti. Uporabite pregledovalnik pripomb v pregledovalniku, da potrdite, da je vsaka povezava resnična pripomba /Link, katere /Rect objema sidrno besedilo in leži nad vsebino, ki je zdaj le navadno izrisani glifi in ne več s strani obrazca izrisan XFA. Ta kombinacija – oblikovano statično besedilo in resnične pripombe povezav na pravih pravokotnikih – je tisto, zaradi česar sploščeni dokument preživi pogone XFA, ki jih ne potrebuje več.

Sploščevanje samih polj – besedilnih polj, potrditvenih polj in seznamov izbir, ki obdajajo to bogato besedilo – je obravnavano v našem vodiču o sploščevanju obrazcev XFA v gradnike AcroForm. Za širšo zgodbo o ročnem ustvarjanju in postavljanju pripomb povezav (poleg tistih, ki jih ustvari pot sploščevanja) si oglejte članek delo s pripombami PDF v HotPDF. Oboje gradi na istem modelu pripomb in obrazcev, ki je na voljo s komponento HotPDF Component za Delphi in C++Builder.