Technical Article

Ravnanje XFA hiperlinkova obogaćenog teksta u PDF linkove u Delphi-ju

XFA, odnosno XML Forms Architecture, je zastarela. ISO 32000-1 je navodi u §12.7 sa napomenom da je uklonjena iz PDF 2.0 standarda, a moderni čitači gase svoje XFA mehanizme jedan po jedan. Ništa od toga nije ispraznilo arhive. Vladini obrasci za unos podataka, prijave osiguranja i bankovni izvodi pisani su kao XFA tokom boljeg dela dve decenije, i te datoteke i danas stižu u sandučiće i procese obrade dokumenata. Kada čitač koji ih je ranije renderovao prestane to da radi, obrazac se pretvara u praznu stranicu sa rezervisanim mestom „molimo otvorite u drugom čitaču”. Trajno rešenje je ravnanje XFA u statični PDF sadržaj koji svaki čitač može da iscrta.

Težak deo tog ravnanja nisu polja. Polja za tekst i polja za potvrdu se sasvim čisto mapiraju na AcroForm vidžete. Težak deo je obogaćeni tekst koji XFA čuva unutar elementa za crtanje, u bloku <exData contentType="text/html">. Taj blok je podskup HTML-a sa ugrađenim stilovima i, često, sidrima. Prenošenje toga na stranicu znači reprodukciju i stilizovanog teksta i aktivnih hiperlinkova, a hiperlinkovi su tačka na kojoj većina implementacija tiho odustaje.

Kako XFA obogaćeni tekst zapravo izgleda

Telo exData je mali deo XHTML-a. Pasus je <p>; stilizovani opseg karaktera je <span> sa sopstvenim ugrađenim CSS-om za težinu, stil, boju i veličinu; a hiperlink je <a href="..."> koji obavija svoj vidljivi tekst. Jedna linija može da sadrži nekoliko opsega zaredom, svaki sa različitim stilom, a jedan od njih može biti sidro. Stilizovanje nije ukras koji se može odbaciti. Klauzula prikazana podebljano crvenom bojom jer predstavlja pravno upozorenje mora ostati podebljana i crvena i nakon ravnanja, inače poravnati dokument pogrešno predstavlja original.

Dakke, mehanizam za ravnanje ne može da tretira blok kao jedan tekstualni niz. On mora da prođe kroz unutrašnju strukturu, reši efektivni stil svake staze slaganjem ugrađenog CSS-a opsega preko osnovnog fonta elementa crtanja i rasporedi staze jednu za drugom duž linije. HotPDF modelira svaki od ovih raspoređenih fragmenata kao interni zapis TXFARichRun. Zapis nosi tekst staze, njen rešeni stil, njenu izmerenu kutiju i, za sidro, Href na koji ukazuje.

Raspoređivanje staza sleva nadesno

Pozicioniranje je faza u kojoj obogaćeni tekst prestaje da bude problem parsiranja i postaje problem slaganja teksta. Staze dele liniju, tako da svaka staza počinje tamo gde se prethodna završila. Ne postoji oznaka koja beleži te pozicije; one se moraju izmeriti. Interna rutina mehanizma LayoutRichText meri svaku stazu istim metrikama fonta kojima će je kasnije oslikati, a zatim postavlja horizontalni pomak staze na tekući zbir svih prethodnih širina staza. Prva staza počinje na početku kutije za crtanje, druga staza počinje na širini prve staze, treća na kombinovanoj širini prve dve, i tako dalje duž linije.

Zbog toga je poravnanje fonta pri merenju veoma važno. Prolaz rasporeda meri pomake; odvojeni prolaz renderovanja crta glifove. Ako se ova dva prolaza ne slažu oko fonta, kutije koje je raspored izračunao neće se nalaziti ispod glifova koje renderer crta. HotPDF ih drži usklađenim tako što mapira rešeni stil svake staze na specifikaciju fonta, kroz interni pomoćnik RunStyleToFontSpec, koji odgovara sopstvenim podrazumevanim vrednostima renderera za Arial na 10 tačaka. Izmereni pomak i nacrtani tekst se tada slažu, a izračunata kutija staze zaista pokriva karaktere koje čitalac 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 staze sidra do PDF Link napomene

Hiperlink u gotovom PDF-u nije deo sadržaja stranice. To je poseban objekat, Link napomena, opisan u standardu ISO 32000-1 §12.5.6.5. Napomena ima /Rect koji definiše pravougaonik na stranici na koji se može kliknuti i akciju koja se pokreće kada se klikne na pravougaonik. Za spoljni link akcija je URI akcija: /S /URI sa ciljnom adresom kao tekstualnim nizom /URI. Vidljivi tekst ispod je običan sadržaj stranice; napomena je nevidljiva aktivna zona postavljena preko njega.

Putanja ravnanja prati upravo ovaj model. Kada staza nosi Href, HotPDF prvo crta stilizovani tekst, a zatim gradi Link napomenu preko kutije te staze. Javna pristupna tačka za tu napomenu je metoda stranice AddURILink, koja kreira objekat /Type /Annot /Subtype /Link sa akcijom /URI i vraća rečnik napomena. Njen pravougaonik je izmerena kutija staze, prevedena iz lokalnih koordinata elementa crtanja u koordinate stranice. Rezultat je link koji leži precizno na tekstu sidra i nigde drugde.

// 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 kutija osetljiva na klik mora dolaziti iz izmerenih širina

Primamljivo je zamisliti lociranje linka pretraživanjem stranice za njegovim vidljivim tekstom i crtanjem pravougaonika oko onoga što se pronađe. To ne radi, a razlog je fundamentalan za način na koji se poravnati tekst čuva. Stilizovane staze se iscrtavaju sa ugrađenim podskupovima fontova. Podskup fonta ponovo numeriše glifove koje zadržava, tako da tok sadržaja stranice sadrži heksadecimalne CID kodove, a ne originalne kodove karaktera. Bajtovi na stranici nisu slova koja čovek čita i ne mogu se pretraživati kao tekst. Pretraga za natpisom sidra ne pronalazi ništa, jer taj natpis ne postoji kao doslovni tekst nigde u toku.

Jedini pouzdan oslonac za pravougaonik je geometrija koju je prolaz rasporeda već proizveo. Pomak i izmerena širina svake staze su izračunati tokom protoka linije, pre nego što je bilo koji glif ponovo numerisan, i oni opisuju gde će se tekst fizički pojaviti. HotPDF stoga uzima pravougaonik linka direktno iz postavljene kutije staze, a ne iz bilo kakve pretrage teksta. Pošto je merenje koristilo font renderovanja, kutija je ispravna bez obzira na podskupove. Geometrija preživljava kodiranje; tekst ne. To je čitav argument za pozicioniranje na osnovu izmerene širine, i to je razlog zašto program za ravnanje koji pokušava naknadno da ubaci linkove pretragom teksta proizvodi aktivne zone koje se pomeraju ili nestaju.

Pokretanje ravnanja iz vašeg koda

Za PDF koji već sadrži XFA paket, pristupna tačka je FlattenLoadedXFA. Učitajte dokument, pozovite metodu i sačuvajte rezultat. Parametar Editable odlučuje šta se dešava sa poljima obrasca: prosledite True da biste ih zadržali kao AcroForm vidžete koji se mogu popuniti, ili False da biste svaki vidžet označili samo za čitanje, tako da izlaz bude zamrznuti zapis. Blokovi za crtanje obogaćenog teksta, sa svojim stilizovanim stazama i link napomenama, proizvode se u oba slučaja. Funkcija vraća broj vidžeta koje je emitovala.

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;

Uvek pročitajte XFAFlattenWarnings nakon poziva. Lista se prazni na početku svakog ravnanja i akumulira liniju za svaki element koji mehanizam odbije da renderuje: nepodržanu vrstu polja, sliku za crtanje koja se ne može dekodirati, exData blok bez upotrebljivih opsega. Ništa od toga ne izaziva izuzetak, tako da je prazna lista upozorenja dokaz da je sve mapirano, a lista koja nije prazna govori vam tačno koje originale treba da pregledate. Kada držite sirovi XFA kao XDP bajtove, a ne kao učitani PDF, srodna metoda ApplyXFAAsAcroForm uzima te bajtove direktno i deli istu putanju koda i isto ponašanje upozorenja. Komplementarna metoda AddXFAPacket medoda ide u drugom smeru, ugrađujući XFA paket u dokument koji gradite.

Potvrda rezultata u čitaču

Otvorite poravnatu datoteku u Acrobat-u ili bilo kom trenutnom čitaču i proverite dve stvari. Prvo, da je obogaćeni tekst renderovan sa netaknutim stilom: podebljane staze su podebljane, obojene staze nose svoju boju, a opsezi leže u ispravnom redosledu na liniji umesto da se preklapaju ili izlaze iz kutije. Drugo, da su hiperlinkovi aktivni. Pređite mišem preko sidra i statusna traka bi trebalo da prikaže ciljnu adresu; kliknite na nju i URI akcija bi trebalo da je otvori. Koristite inspektor napomena u čitaču da potvrdite da je svaka od njih prava /Link napomena čiji pravougaonik /Rect grli tekst sidra, ležeći preko sadržaja koji su sada obični nacrtani glifovi, a ne XFA renderovan iz obrasca. Ta kombinacija, stilizovani statički tekst plus stvarne Link napomene na ispravnim pravougaonicima, jeste ono što čini da poravnati dokument nadživi XFA mehanizme koji mu više nisu potrebni.

Ravnanje samih polja, tekstualnih kutija, polja za potvrdu i lista izbora koji okružuju ovaj obogaćeni tekst, pokriveno je u našem vodiču o ravnanju XFA obrazaca u AcroForm vidžete. Za širu priču o ručnoj izgradnji i postavljanju Link napomena, izvan onih koje stvara putanja ravnanja, pogledajte rad sa PDF napomenama u HotPDF-u. Oba se oslanjaju na isti model napomena i obrazaca koji se isporučuje sa HotPDF komponentom za Delphi i C++Builder.