Technical Article

XFA-rikasteksti-hyperlinkkien litistäminen PDF-linkeiksi Delphissä

XFA eli XML-lomakearkkitehtuuri on vanhentunut. ISO 32000-1 sisältää sen kohdassa §12.7 huomautuksella, että se on poistettu PDF 2.0 -versiosta, ja nykyaikaiset katseluohjelmat luopuvat XFA-moottoreistaan yksi kerrallaan. Mikään tästä ei ole tyhjentänyt arkistoja. Viranomaisten lomakkeet, vakuutushakemukset ja pankkitilitiedot luotiin XFA-muodossa lähes kahden vuosikymmenen ajan, ja näitä tiedostoja saapuu edelleen sähköposteihin ja dokumenttiputkiin tänään. Kun katseluohjelma, joka aiemmin renderöi ne, lakkaa tekemästä niin, lomake muuttuu tyhjäksi sivuksi, jossa on "avaa toisessa lukijassa" -paikkamerkki. Kestävä korjaus on litistää XFA staattiseksi PDF-sisällöksi, jonka mikä tahansa lukija voi piirtää.

Tämän litistämisen vaikein osa ei ole kentät. Tekstilaatikot ja valintaruudut kuvautuvat AcroForm-elementeiksi riittävän siististi. Vaikein osa on rikasteksti, jonka XFA tallentaa piirtoelementin sisälle, <exData contentType="text/html"> -lohkoon. Tämä lohko on HTML-alijoukko, jossa on sisäinen muotoilu ja usein ankkureita. Sen saaminen sivulle tarkoittaa sekä muotoillun tekstin että toimivien hyperlinkkien toistamista, ja hyperlinkit ovat se kohta, jossa useimmat toteutukset hiljaa luovuttavat.

Miltä XFA-rikasteksti todellisuudessa näyttää

exData-runko on pieni siivu XHTML-koodia. Kappale on <p>; muotoiltu merkkijono on <span> omilla sisäisillä CSS-tyyleillään painolle, asennolle, värille ja koolle; ja hyperlinkki on <a href="...">, joka käärii näkyvän tekstinsä. Yksi rivi voi sisältää useita peräkkäisiä span-elementtejä, joilla on eri muotoilu, ja yksi niistä voi olla ankkuri. Muotoilu ei ole koristelu, joka voidaan jättää pois. Lauseke, joka renderöidään lihavoituna punaisena, koska se on laillinen varoitus, on säilytettävä lihavoituna ja punaisena litistämisen jälkeen, tai litistetty dokumentti vääristää alkuperäistä.

Joten litistysmoottori ei voi käsitellä lohkoa yhtenä merkkijonona. Sen on käytävä läpi sisäinen rakenne, ratkaistava kunkin ajon tehokas tyyli kerrostamalla span-elementin sisäinen CSS piirtoelementin perusfontin päälle, ja asetettava ajot peräkkäin riville. HotPDF mallintaa jokaisen näistä asetelluista fragmenteista sisäisenä TXFARichRun-tietueena. Tietue kantaa ajon tekstin, sen ratkaistun tyylin, sen mitatun laatikon ja ankkurille Href-kohteen, johon se osoittaa.

Ajojen asettelu vasemmalta oikealle

Sijoittelu on se kohta, jossa rikasteksti lakkaa olemasta jäsennysongelma ja muuttuu ladontaongelmaksi. Ajot jakavat rivin, joten jokainen ajo alkaa siitä, mihin edellinen päättyi. Ei ole merkintää, joka tallentaisi nämä sijainnit; ne on mitattava. Moottorin sisäinen LayoutRichText-rutiini mittaa jokaisen ajon samoilla fonttimetriikoilla, jotka myöhemmin piirtävät sen, ja asettaa sitten ajon vaakasuoran siirtymän kaikkien aiempien ajoleveyksien juoksevaan summaan. Ajo yksi alkaa piirtolaatikon origosta, ajo kaksi alkaa ajon yksi leveydestä, ajo kolme kahden ensimmäisen yhdistetystä leveydestä ja niin edelleen rivin yli.

Tästä syystä mittausfontin kohdistuksella on niin suuri merkitys. Asetteluvaihe mittaa etenemää; erillinen renderöintivaihe piirtää glyfit. Jos nämä kaksi vaihetta ovat eri mieltä fontista, asettelun laskemat laatikot eivät sovi renderöijän maalaamien glyfien alle. HotPDF pitää ne tahdissa kuvaamalla kunkin ajon ratkaistun tyylin fonttimäärittelyksi sisäisen RunStyleToFontSpec-apurin kautta, joka vastaa renderöijän omia oletusarvoja. Mitattu etenemä ja piirretty teksti ovat silloin yhtä mieltä, ja ajon laskettu laatikko kattaa aidosti merkit, jotka lukija näkee.

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

Ankkuriajosta PDF-linkkiannotointiin

Valmiissa PDF-tiedostossa oleva hyperlinkki ei ole osa sivun sisältöä. Se on erillinen objekti, linkkiannotointi, joka kuvataan ISO 32000-1 -standardin kohdassa §12.5.6.5. Annoinnilla on /Rect, joka määrittelee napsautettavan suorakulmion sivulla, ja toiminto, joka käynnistyy, kun suorakulmiota napsautetaan. Ulkoiselle linkille toiminto on URI-toiminto: /S /URI kohdeosoitteen ollessa sen /URI-merkkijono. Alla näkyvä teksti on tavallista sivun sisältöä; annotointi on sen päälle asetettu näkymätön aktiivinen alue.

Litistyspolku noudattaa täsmälleen tätä mallia. Kun ajo kantaa Href-kohdetta, HotPDF ensin piirtää muotoillun tekstin ja rakentaa sitten linkkiannotoinnin ajon laatikon päälle. Tämän annoonin julkinen aloituspiste on sivumetodi AddURILink, joka luo /Type /Annot /Subtype /Link -objektin URI-toiminnolla ja palauttaa annotointisanakirjan. Sen suorakulmio on ajon mitattu laatikko, muunnettuna piirtoelementin paikallisista koordinaateista sivukoordinaateiksi. Tuloksena on linkki, joka osuu tarkasti ankkuritekstiin eikä mihinkään muualle.

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

Miksi osuma-alueen on oltava peräisin mitatuista leveyksistä

On houkuttelevaa kuvitella linkin paikantaminen etsimällä sivulta sen näkyvää tekstiä ja piirtämällä suorakulmio löydetyn ympärille. Se ei toimi, ja syy on perustavanlaatuinen sille, miten litistetty teksti tallennetaan. Muotoillut ajot maalataan upotetuilla ositetuilla fonteilla. Ositettu fontti numeroi säilyttämänsä glyfit uudelleen, joten sivun sisältövirta sisältää heksadesimaalisia CID-koodeja, ei alkuperäisiä merkkikoodeja. Sivun tavut eivät ole kirjaimia, joita ihminen lukee, eivätkä ne ole haettavissa tekstinä. Haku ankkurin otsikolle ei löydä mitään, koska kyseistä otsikkoa ei ole olemassa kirjaimellisena tekstinä missään kohdassa virtaa.

Ainoa luotettava ankkuri suorakulmiolle on geometria, jonka asetteluvaihe jo tuotti. Kunkin ajon siirtymä ja mitattu leveys laskettiin rivin virtautuksen aikana, ennen kuin glyfejä numeroitiin uudelleen, ja ne kuvaavat sitä, missä teksti fyysisesti näkyy. HotPDF ottaa siksi linkkisuorakulmion suoraan ajon asetetusta laatikosta eikä mistään tekstihakutoiminnosta. Koska mittaus käytti renderöintifonttia, laatikko on oikein osituksesta riippumatta. Geometria selviää koodauksesta; teksti ei. Se on koko peruste mitatun leveyden mukaiselle sijoittelulle, ja se on syy siihen, miksi tekstihakujen avulla linkkejä jälkiasentamaan pyrkivä litistys tuottaa osuma-alueita, jotka ajelehtivat tai katoavat.

Litistämisen ajaminen koodistasi

Jos PDF-tiedosto sisältää jo XFA-paketin, aloituspiste on FlattenLoadedXFA. Lataa dokumentti, kutsu metodia ja tallenna tulos. Parametri Editable päättää, mitä lomakekentille tapahtuu: välitä True pitääksesi ne täytettävinä AcroForm-elementteinä tai False merkitäksesi jokaisen elementin vain luku -tilaan, jolloin tuloste on jäädytetty tietue. Rikastekstipiirtoelementit, joissa on muotoillut ajot ja linkkiannotoinnit, tuotetaan kummassakin tapauksessa. Funktio palauttaa vietyjen elementtien määrän.

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;

Lue aina XFAFlattenWarnings kutsun jälkeen. Luettelo tyhjennetään jokaisen litistämisen alussa, ja se kerää rivin jokaiselle elementille, jota moottori kieltäytyi renderöimästä: tukematon kenttätyyppi, piirtokuva, joka ei dekoodaudu, tai exData-lohko, jossa ei ole käyttökelpoisia osioita. Mikään näistä ei nosta poikkeusta, joten tyhjä varoitusluettelo on todisteesi siitä, että kaikki kuvautui oikein, ja ei-tyhjä kertoo tarkalleen, mitkä alkuperäiset tiedostot on tarkastettava. Kun hallitset raakaa XFA:ta XDP-tavuina ladatun PDF-tiedoston sijaan, sisarmetodi ApplyXFAAsAcroForm ottaa nämä tavut suoraan ja jakaa saman koodipolun ja varoituskäyttäytymisen. Komplementaarinen AddXFAPacket-metodi menee toiseen suuntaan ja upottaa XFA-paketin rakenteilla olevaan dokumenttiin.

Tuloksen vahvistaminen katseluohjelmassa

Avaa litistetty tiedosto Acrobatissa tai missä tahansa nykyisessä katseluohjelmassa ja tarkista kaksi asiaa. Ensinnäkin rikasteksti renderöitiin muotoilun säilyessä: lihavoidut ajot ovat lihavoituja, värilliset ajot kantavat värinsä ja span-elementit istuvat oikeassa järjestyksessä rivillä sen sijaan, että ne menisivät päällekkäin tai ajaisivat ulos laatikosta. Toiseksi hyperlinkit ovat toiminnassa. Vie hiiri ankkurin päälle, ja tilarivin pitäisi näyttää kohdeosoite; napsauta sitä, ja URI-toiminnon pitäisi avata se. Käytä katseluohjelman annotointitarkastinta varmistaaksesi, että jokainen on aito /Link-annotointi, jonka /Rect syleilee ankkuritekstiä, istuen sisällön päällä, joka on nyt pelkkiä maalattuja glyfejä eikä lomakkeen renderöimää XFA-tietoa. Tämä yhdistelmä – muotoiltu staattinen teksti plus todelliset linkkiannotoinnit oikeissa suorakulmioissa – tekee litistetystä dokumentista pitkäikäisemmän kuin XFA-moottorit, joita se ei enää tarvitse.

Itse kenttien – tekstilaatikoiden, valintaruutujen ja valintaluetteloiden – litistäminen, jotka ympäröivät tätä rikastekstiä, käsitellään ohjeessamme XFA-lomakkeiden litistämisestä AcroForm-elementeiksi. Laajemmasta tarinasta linkkiannotointien rakentamisesta ja sijoittamisesta käsin, niiden ulkopuolella, joita litistyspolku tuottaa, on tietoa oppaassa PDF-annotointien parissa työskentelystä HotPDF:ssä. Molemmat perustuvat samaan annotointi- ja lomakemalliin, joka toimitetaan Delphin ja C++Builderin HotPDF Componentin mukana.