XFA, XML Forms Architecture, on vanhentunut. ISO 32000-1 sisältää sen kohdassa §12.7 huomautuksella, että se on poistettu PDF 2.0:sta, ja nykyaikaiset katseluohjelmat pudottavat XFA-moottoreitaan yksitellen. Mikään tästä ei ole tyhjentänyt arkistoja. Hallituksen vastaanottolomakkeita, vakuutushakemuksia ja tiliotteita laadittiin XFA-muodossa suurimman osan kahdesta vuosikymmenestä, ja näitä tiedostoja saapuu yhä tänäkin päivänä sähköposteihin ja asiakirjaputkiin. Kun katseluohjelma, joka ennen renderöi ne, lopettaa sen, lomake muuttuu tyhjäksi sivuksi, jossa on paikkamerkki "avaa toisessa lukijassa" (please open in a different reader). Kestävä korjaus on litistää (flatten) XFA staattiseksi PDF-sisällöksi, jonka kuka tahansa lukija voi maalata
Tuon litistämisen vaikea osa eivät ole kentät. Tekstikentät ja valintaruudut yhdistyvät AcroForm-widgetteihin riittävän siististi. Vaikea osa on rikasteksti, jonka XFA tallentaa piirtoelementin (draw element) sisään, <exData contentType="text/html"> -lohkoon. Tuo lohko on HTML-osajoukko (subset), jossa on sisäinen muotoilu (inline styling) ja usein ankkureita. Sen saaminen sivulle tarkoittaa sekä muotoillun tekstin että elävien hyperlinkkien toistamista, ja hyperlinkit ovat kohta, jossa useimmat toteutukset luovuttavat hiljaa
Miltä XFA-rikasteksti todella näyttää
exData-runko on pieni viipale XHTML:ää. Kappale on <p>; muotoiltu merkkijakso on <span>, jolla on oma sisäinen CSS painolle, asennolle (posture), värille ja koolle; ja hyperlinkki on <a href="...">, joka kietoo sen näkyvän tekstin. Yhdellä rivillä voi olla useita jaksoja peräkkäin, kullakin erilainen muotoilu, ja yksi niistä voi olla ankkuri. Muotoilu ei ole koristelu, joka voidaan pudottaa pois. Lauseke, joka on renderöity lihavoidulla punaisella, koska se on oikeudellinen varoitus, on pysyttävä lihavoituna ja punaisena litistämisen jälkeen, tai litistetty asiakirja vääristää alkuperäistä
Joten litistysmoottori ei voi käsitellä lohkoa yhtenä merkkijonona. Sen on käytävä läpi sisäinen rakenne, ratkaistava jokaisen jakson (run) efektiivinen tyyli kerrostamalla jakson sisäinen CSS piirtoelementin perusfontin päälle ja ladottava jaksot toinen toisensa perään riville. HotPDF mallintaa jokaisen näistä ladotuista fragmenteista sisäisenä TXFARichRun-tietueena. Tietue kantaa jakson tekstin, sen ratkaistun tyylin, sen mitatun laatikon ja ankkurille sen Href:n, johon se osoittaa
Jaksojen latominen vasemmalta oikealle
Sijoittelu on kohta, jossa rikasteksti lakkaa olemasta jäsennysongelma (parsing problem) ja muuttuu ladontaongelmaksi. Jaksot jakavat rivin, joten jokainen jakso alkaa siitä, mihin edellinen päättyi. Ei ole olemassa merkkauskieltä (markup), joka tallentaisi nuo sijainnit; ne on mitattava. Moottorin sisäinen LayoutRichText-rutiini mittaa jokaisen jakson samoilla fonttimetriikoilla, jotka myöhemmin maalaavat sen, ja asettaa sitten jakson vaakasuuntaisen siirtymän kaikkien aiempien jaksoleveyksien juoksevaan summaan. Jakso yksi alkaa piirtolaatikon origosta, jakso kaksi alkaa jakson yksi leveydestä, jakso kolme kahden ensimmäisen yhdistetystä leveydestä ja niin edelleen koko rivin poikki
Tästä syystä mittausfontin kohdistuksella on niin paljon merkitystä. Asetteluvaihe (layout pass) mittaa etenemisiä (advances); erillinen renderöintivaihe piirtää glyyfejä. Jos nämä kaksi vaihetta ovat eri mieltä fontista, asettelun laskemat laatikot eivät istu renderöijän maalaamien glyyfien alla. HotPDF pitää ne tahdissa kartoittamalla kunkin jakson ratkaistun tyylin fonttimääritykseen sisäisen RunStyleToFontSpec-apulaisen kautta, joka vastaa renderöijän omia oletusasetuksia (Arial, 10 pistettä). Mitattu eteneminen ja piirretty teksti ovat silloin yhtä mieltä, ja jakson laskettu laatikko aidosti peittää lukijan näkemät merkit
// 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;
Ankkurijaksosta PDF-Link-merkinnäksi
Hyperlinkki valmiissa PDF-tiedostossa ei ole osa sivun sisältöä. Se on erillinen objekti, Link-merkintä, joka on kuvattu kohdassa ISO 32000-1 §12.5.6.5. Merkinnässä on /Rect, joka määrittelee napsautettavan suorakulmion sivulla, ja toiminto (action), joka laukeaa, kun suorakulmiota napsautetaan. Ulkoiselle linkille toiminto on URI-toiminto: /S /URI, jonka kohdeosoite on sen /URI-merkkijonona. Näkyvä teksti sen alla on tavallista sivun sisältöä; merkintä on näkymätön aktiivinen alue (hot zone), joka on asetettu sen päälle
Litistyspolku noudattaa täsmälleen tätä mallia. Kun jakso kantaa Href-arvoa, HotPDF piirtää ensin muotoillun tekstin ja rakentaa sitten Link-merkinnän jakson laatikon päälle. Julkinen aloituskohta tuolle merkinnälle on sivun metodi AddURILink, joka luo /Type /Annot /Subtype /Link -objektin /URI-toiminnolla ja palauttaa merkintäsanakirjan (annotation dictionary). Sen suorakulmio on jakson mitattu laatikko, käännettynä piirtoelementin paikallisista koordinaateista sivun koordinaatteihin. Tuloksena on linkki, joka laskeutuu tarkalleen ankkuritekstiin eikä minnekää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 (hit box) on tultava mitatuista leveyksistä
On houkuttelevaa kuvitella sijoittavansa linkki etsimällä sivulta sen näkyvä teksti ja piirtämällä suorakulmio minkä tahansa löytyneen ympärille. Se ei toimi, ja syy on perustavanlaatuinen sille, miten litistetty teksti tallennetaan. Muotoillut jaksot maalataan upotetuilla osajoukko-fonteilla (subset fonts). Osajoukko-fontti numeroi uudelleen glyyfit, jotka se säilyttää, joten sivun sisältövirta (content stream) pitää sisällään heksadesimaalisia CID-koodeja, ei alkuperäisiä merkkikoodeja. Sivulla olevat tavut eivät ole kirjaimia, joita ihminen lukee, eivätkä ne ole haettavissa tekstinä. Haku ankkurin kuvatekstillä ei löydä mitään, koska kyseistä kuvatekstiä ei ole olemassa kirjaimellisena tekstinä missään virrassa
Ainoa luotettava ankkuri suorakulmiolle on geometria, jonka asetteluvaihe (layout pass) jo tuotti. Kunkin jakson siirtymä ja mitattu leveys laskettiin virratettaessa riviä, ennen kuin yhtäkään glyyfiä numeroitiin uudelleen, ja ne kuvaavat, missä teksti fyysisesti ilmestyy. Siksi HotPDF ottaa linkkisuorakulmion suoraan jakson alaslasketusta laatikosta (laid-down box) minkään tekstin haun sijaan. Koska mittauksessa käytettiin renderöintifonttia, laatikko on oikea riippumatta alijoukon muodostamisesta (subsetting). Geometria selviää koodauksesta; teksti ei. Se on koko peruste mitatun leveyden sijoittelulle, ja siksi litistäjä (flattener), joka yrittää asentaa linkkejä jälkikäteen tekstihaulla, tuottaa osuma-alueita (hit zones), jotka ajelehtivat tai katoavat
Litistyksen ohjaaminen koodistasi
PDF-tiedostolle, joka sisältää jo XFA-paketin, aloituskohta on FlattenLoadedXFA. Lataa asiakirja, kutsu metodia ja tallenna tulos. Editable-parametri päättää, mitä lomakekentille tapahtuu: välitä True pitääksesi ne täytettävinä AcroForm-widgetteinä, tai False merkitäksesi jokaisen widgetin vain luku -tilaan, jotta tuloste on jäädytetty tietue. Rikastekstin piirtolohkot (rich-text draw blocks) muotoiltuine jaksoineen ja linkkimerkintöineen tuotetaan joka tapauksessa. Funktio palauttaa sen lähettämien widgettien 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 litistyksen alussa, ja siihen kertyy rivi jokaiselle elementille, jota moottori kieltäytyi renderöimästä: tukematon kenttätyyppi, piirtokuva, joka ei purkaudu, exData-lohko, jossa ei ole käyttökelpoisia jaksoja. Mikään näistä ei nosta poikkeusta, joten tyhjä varoituslista on todisteesi siitä, että kaikki kartoitettiin, ja ei-tyhjä lista kertoo tarkalleen, mitkä alkuperäiskappaleet on tarkistettava. Kun pidät raakaa XFA:ta XDP-tavuina (XDP bytes) ladatun PDF:n sijaan, sisarmetodi ApplyXFAAsAcroForm ottaa kyseiset tavut suoraan ja jakaa saman koodipolun ja saman varoituskäyttäytymisen. Täydentävä AddXFAPacket-metodi kulkee toiseen suuntaan ja upottaa XFA-paketin asiakirjaan, jota olet rakentamassa
Tuloksen vahvistaminen lukijassa
Avaa litistetty tiedosto Acrobatissa tai missä tahansa nykyisessä katseluohjelmassa ja tarkista kaksi asiaa. Ensinnäkin rikasteksti on renderöity sen muotoilu säilyttäen: lihavoidut jaksot ovat lihavoituja, värilliset jaksot kantavat värinsä, ja jaksot istuvat oikeassa järjestyksessä rivillä sen sijaan, että ne menisivät päällekkäin tai juoksisivat ulos laatikosta. Toiseksi hyperlinkit ovat eläviä. Vie hiiren osoitin ankkurin päälle, ja tilapalkin tulisi näyttää kohdeosoite; napsauta sitä, ja URI-toiminnon tulisi avata se. Käytä katseluohjelman merkintätarkastajaa (annotation inspector) vahvistaaksesi, että jokainen on aito /Link-merkintä, jonka /Rect halaa ankkuritekstiä ja istuu sisällön päällä, joka on nyt vain maalattuja glyyfejä lomakkeen renderöimän XFA:n sijaan. Tämä yhdistelmä, muotoiltu staattinen teksti ja oikeat Link-merkinnät oikeilla suorakulmioilla, saa litistetyn asiakirjan elämään pidempään kuin XFA-moottorit, joita se ei enää tarvitse
Itse kenttien, tätä rikastekstiä ympäröivien tekstikenttien, valintaruutujen ja valintalistojen litistäminen käsitellään katsauksessamme XFA-lomakkeiden litistämisestä AcroForm-widgeteiksi. Laajemman tarinan Link-merkintöjen rakentamisesta ja sijoittamisesta käsin, yli niiden, joita litistyspolku luo, löydät artikkelista työskentely PDF-merkintöjen kanssa HotPDF:ssä. Molemmat rakentuvat samalle merkintä- ja lomakemallille (annotation and forms model), joka toimitetaan HotPDF Componentin mukana Delphille ja C++Builderille