Technical Article

Izravnavanje XFA hiperveza obogaćenog teksta u PDF veze u Delphiu

XFA, arhitektura XML obrazaca (XML Forms Architecture), je zastarjela. ISO 32000-1 je navodi u §12.7 uz napomenu da je uklonjena iz PDF-a 2.0, a moderni preglednici jedan po jedan odbacuju svoje XFA motore. Ništa od toga nije ispraznilo arhive. Vladini obrasci za prijavu, zahtjevi za osiguranje i bankovni izvodi pisani su kao XFA tijekom boljeg dijela dva desetljeća, i te datoteke još uvijek pristižu u sandučiće i cjevovode dokumenata danas. Kada preglednik koji ih je prikazivao prestane to činiti, obrazac se pretvara u praznu stranicu s rezerviranim mjestom "molimo otvorite u drugom čitaču". Trajno rješenje je izravnati (flatten) XFA u statični PDF sadržaj koji svaki čitač može iscrtati.

Težak dio tog izravnavanja nisu polja. Tekstualni okviri i okviri za potvrdu prilično se čisto mapiraju na AcroForm widgete. Težak dio je obogaćeni tekst (rich text) koji XFA pohranjuje unutar elementa crtanja, u bloku <exData contentType="text/html">. Taj blok je podskup HTML-a s ugrađenim stilovima i, često, sidrima (anchors). Postavljanje na stranicu znači reprodukciju stiliziranog teksta i aktivnih hiperveza, a hiperveze su mjesto gdje većina implementacija tiho odustaje.

Kako XFA obogaćeni tekst zapravo izgleda

Tijelo exData je mali isječak XHTML-a. Odlomak je <p>; stilizirani niz znakova je <span> s vlastitim ugrađenim CSS-om za debljinu, stil, boju i veličinu; a hiperveza je <a href="..."> koja omotava svoj vidljivi tekst. Jedan redak može sadržavati nekoliko raspona (spans) u nizu, svaki s različitim stilom, a jedan od njih može biti sidro (anchor). Stiliziranje nije ukras koji se može odbaciti. Klauzula prikazana podebljano crvenom bojom jer je pravno upozorenje mora ostati podebljana i crvena nakon izravnavanja, inače izravnani dokument pogrešno predstavlja izvornik.

Stoga motor za izravnavanje ne može tretirati blok kao jedan niz znakova. On mora proći kroz ugrađenu strukturu, razriješiti efektivni stil svakog niza (run) slojevanjem ugrađenog CSS-a raspona preko osnovnog fonta elementa crtanja te rasporediti nizove jedan za drugim duž retka. HotPDF modelira svaki od ovih raspoređenih fragmenata kao interni zapis TXFARichRun. Zapis nosi tekst niza, njegov razriješeni stil, njegov izmjereni okvir i, za sidro, Href na koji pokazuje.

Raspoređivanje nizova s lijeva na desno

Pozicioniranje je mjesto gdje obogaćeni tekst prestaje biti problem parsiranja i postaje problem slaganja teksta (typesetting). Nizovi dijele redak, pa svaki niz počinje tamo gdje je prethodni završio. Nema oznaka koje bilježe te položaje; oni se moraju izmjeriti. Interna rutina motora LayoutRichText mjeri svaki niz s istom metrikom fonta kojom će se kasnije crtati, a zatim postavlja vodoravni odmak niza na tekući zbroj svih prethodnih širina niza. Prvi niz počinje na početku okvira crtanja, drugi niz počinje na širini prvog niza, treći na kombiniranoj širini prva dva i tako dalje duž retka.

This is why measurement font alignment matters so much. The layout pass measures advances; a separate render pass draws glyphs. If those two passes disagree about the font, the boxes the layout computed will not sit under the glyphs the renderer paints. 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 sidrenog niza do bilješke PDF veze

Hiperveza u gotovom PDF-u nije dio sadržaja stranice. To je zaseban objekt, bilješka veze (Link annotation), opisana u ISO 32000-1 §12.5.6.5. Bilješka ima /Rect koji definira klikabilni pravokutnik na stranici i akciju koja se aktivira kada se na pravokutnik klikne. Za vanjsku vezu akcija je URI akcija: /S /URI s ciljnom adresom kao svojim /URI nizom znakova. Vidljivi tekst ispod je običan sadržaj stranice; bilješka je nevidljiva vruća zona položena preko njega.

Putanja izravnavanja prati točno ovaj model. Kada niz nosi Href, HotPDF najprije crta stilizirani tekst, a zatim gradi bilješku veze (Link annotation) preko okvira niza. Javna točka ulaza za tu bilješku je metoda stranice AddURILink, koja stvara objekt /Type /Annot /Subtype /Link s URI akcijom i vraća rječnik bilješke. Njegov pravokutnik je izmjereni okvir niza, preveden iz lokalnih koordinata elementa crtanja u koordinate stranice. Rezultat je veza koja slijeće točno na tekst sidra i nigdje drugdje.

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

Zašto okvir pritiska mora doći iz izmjerenih širina

Primamljivo je zamisliti lociranje veze pretraživanjem stranice za njezin vidljivi tekst i crtanjem pravokutnika oko onoga što se pronađe. To ne funkcionira, a razlog je temeljan za način na koji se izravnani tekst pohranjuje. Stilizirani nizovi se crtaju s ugrađenim podskupovima fontova (embedded subset fonts). Podskup fonta ponovno numerira glifove koje zadržava, tako da tok sadržaja stranice sadrži heksadecimalne CID kodove, a ne izvorne kodove znakova. Bajtovi na stranici nisu slova koja čovjek čita i ne mogu se pretraživati kao tekst. Pretraživanje naslova sidra ne pronalazi ništa, jer taj naslov ne postoji kao doslovni tekst nigdje u toku.

Jedino pouzdano sidro za pravokutnik je geometrija koju je prolaz rasporeda već proizveo. Odmak svakog niza i izmjerena širina izračunati su tijekom protoka retka, prije nego što je bilo koji glif ponovno numeriran, i oni opisuju gdje će se tekst fizički pojaviti. HotPDF stoga uzima pravokutnik veze izravno iz postavljenog okvira niza, a ne iz bilo kakvog traženja teksta. Budući da je mjerenje koristilo font iscrtavanja, okvir je točan bez obzira na podskupove. Geometrija preživljava kodiranje; tekst ne. To je cijeli argument za pozicioniranje prema izmjerenoj širini, i to je razlog zašto izravnivač koji pokušava naknadno ugraditi veze pretraživanjem teksta proizvodi zone pritiska (hit zones) koje odstupaju ili nestaju.

Pokretanje izravnavanja iz vašeg koda

Za PDF koji već sadrži XFA paket, točka ulaza je FlattenLoadedXFA. Učitajte dokument, pozovete metodu i spremite rezultat. Parametar Editable odlučuje što se događa s poljima obrasca: proslijedite True kako biste ih zadržali kao widgete obrasca AcroForm koji se mogu ispuniti, ili False kako biste svaki widget označili samo za čitanje tako da je izlaz zamrznuti zapis. Blokovi crtanja obogaćenog teksta, sa svojim stiliziranim nizovima i bilješkama veza, proizvode se u oba slučaja. Funkcija vraća broj widgeta koje je emitirala.

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;

Uvijek pročitajte XFAFlattenWarnings nakon poziva. Popis se čisti na početku svakog izravnavanja i akumulira redak za svaki element koji je motor odbio iscrtati: nepodržanu vrstu polja, sliku crteža koja se ne može dekodirati, blok exData bez upotrebljivih raspona. Ništa od toga ne podiže iznimku, pa je prazan popis upozorenja vaš dokaz da je sve mapirano, a neprazan vam govori točno koje izvornike trebate pregledati. Kada imate sirovi XFA kao XDP bajtove umjesto učitanog PDF-a, srodna metoda ApplyXFAAsAcroForm uzima te bajtove izravno i dijeli istu stazu koda i isto ponašanje upozorenja. Komplementarna metoda AddXFAPacket ide u suprotnom smjeru, ugrađujući XFA paket u dokument koji gradite.

Potvrdite rezultat u čitaču

Otvorite izravnanu datoteku u Acrobat-u, ili bilo kojem trenutnom pregledniku, i provjerite dvije stvari. Prvo, da se obogaćeni tekst iscrtao sa sačuvanim stilovima: podebljani nizovi su podebljani, obojeni nizovi nose svoju boju, a rasponi (spans) se nalaze u ispravnom redoslijedu u retku umjesto da se preklapaju ili prelaze preko okvira. Drugo, da su hiperveze aktivne. Prijeđite mišem preko sidra i statusna traka trebala bi prikazati ciljnu adresu; kliknite je i URI akcija bi je trebala otvoriti. Koristite preglednik bilješki kako biste potvrdili da je svaka od njih stvarna bilješka /Link čiji /Rect obuhvaća tekst sidra, ležeći preko sadržaja koji su sada obični obojani glifovi umjesto XFA-om iscrtane forme. Ta kombinacija, stilizirani statički tekst plus stvarne bilješke veza na ispravnim pravokutnicima, ono je što čini da izravnani dokument nadživi XFA motore koji mu više nisu potrebni.

Izravnavanje samih polja, tekstualnih okvira, okvira za potvrdu i popisa izbora koji okružuju ovaj obogaćeni tekst, pokriveno je u našem vodiču o izravnavanju XFA obrazaca u AcroForm widgete. Za širu priču o ručnoj izradi i postavljanju bilješki veza, osim onih koje generira staza izravnavanja, pogledajte rad s PDF bilješkama u HotPDF-u. Oboje se gradi na istom modelu bilješki i obrazaca koji se isporučuje s HotPDF komponentom za Delphi i C++Builder.