Műszaki cikk

XFA Rich-Text hivatkozások simítása PDF linkekké Delphiben

Az XFA, az XML Forms Architecture elavult (deprecated). Az ISO 32000-1 a 12.7. szakaszban tartalmazza azzal a megjegyzéssel, hogy a PDF 2.0-ból eltávolították, és a modern nézegetők sorra ejtik XFA motorjaikat. Mindez nem ürítette ki az archívumokat. Kormányzati felvételi űrlapokat, biztosítási kérelmeket és banki kivonatokat írtak XFA-ként jó két évtizeden keresztül, és ezek a fájlok még ma is érkeznek a beérkező levelek közé és a dokumentum-csatornákba (pipelines). Amikor a nézegető, amely korábban renderelte őket, felhagy ezzel, az űrlap egy üres oldallá válik egy "kérjük, nyissa meg egy másik olvasóban" helyőrzővel. A tartós megoldás az XFA simítása (flattening) statikus PDF tartalommá, amelyet bármelyik olvasó meg tud festeni

Ennek a simításnak a nehéz része nem a mezők. A szövegdobozok és a jelölőnégyzetek elég tisztán leképeződnek az AcroForm widgetekre. A nehéz rész az a gazdag szöveg (rich text), amelyet az XFA egy rajzelemen belül, egy <exData contentType="text/html"> blokkban tárol. Ez a blokk egy HTML részhalmaz beágyazott stílusokkal és gyakran horgonyokkal (anchors). Ennek az oldalra juttatása azt jelenti, hogy reprodukálni kell a formázott szöveget és az élő hiperhivatkozásokat is, és a hiperhivatkozások azok, ahol a legtöbb megvalósítás csendben feladja

Hogyan is néz ki valójában az XFA rich text

Az exData törzs az XHTML egy kis szelete. Egy bekezdés egy <p>; egy formázott karakterszakasz (span) egy <span> a saját beágyazott CSS-ével a vastagságra, dőlésre, színre és méretre; a hiperhivatkozás pedig egy <a href="...">, amely körbeveszi a látható szövegét. Egyetlen sor több spant is tartalmazhat egymás után, mindegyiket más-más stílussal, és ezek közül az egyik lehet egy horgony. A stílus nem elhagyható díszítés. Egy záradéknak, amelyet vastag piros színnel jelenítenek meg, mert jogi figyelmeztetés, vastagnak és pirosnak kell maradnia a simítás után is, különben a simított dokumentum hamisan adja vissza az eredetit

A simító motor tehát nem kezelheti a blokkot egyetlen karakterláncként. Végig kell járnia a beágyazott struktúrát, fel kell oldania minden egyes futtatás (run) tényleges stílusát úgy, hogy a span beágyazott CSS-ét a rajzelem alap betűtípusára rétegezi, és a futtatásokat egymás után el kell helyeznie a sorban. A HotPDF ezen elrendezett töredékek mindegyikét belső TXFARichRun rekordként modellezi. A rekord tartalmazza a futtatás szövegét, a feloldott stílusát, a mért dobozát, és horgony esetén azt a Href-et, amelyre mutat

A futtatások elrendezése balról jobbra

A pozicionálás az, ahol a rich text már nem elemzési probléma, hanem szedési (typesetting) problémává válik. A futtatások osztoznak egy soron, így minden futtatás ott kezdődik, ahol az előző véget ért. Nincs olyan jelölés, amely rögzítené ezeket a pozíciókat; meg kell őket mérni. A motor belső LayoutRichText rutinja minden futtatást ugyanazokkal a betűtípus-metrikákkal mér meg, amelyekkel később meg is festi, majd beállítja a futtatás vízszintes eltolását az összes korábbi futtatási szélesség futó összegére. Az első futtatás a rajzdoboz origójánál kezdődik, a második az első futtatás szélességénél, a harmadik az első kettő együttes szélességénél, és így tovább a soron

Ezért számít olyan sokat a mérési betűtípus összehangolása. Az elrendezési menet (layout pass) előrehaladásokat mér; egy különálló renderelési menet glifákat rajzol. Ha ez a két menet nem ért egyet a betűtípussal kapcsolatban, az elrendezés által kiszámított dobozok nem fognak a renderelő által festett glifák alatt ülni. A HotPDF úgy tartja őket lépésben, hogy minden futtatás feloldott stílusát egy betűtípus-specifikációra képezi le a belső RunStyleToFontSpec segítőn keresztül, amely megegyezik a renderelő saját alapértelmezett, 10 pontos Arial beállításával. A mért előrehaladás és a megrajzolt szöveg ekkor megegyezik, és egy futtatás kiszámított doboza valóban lefedi azokat a karaktereket, amelyeket az olvasó lát

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

A horgony futtatástól a PDF Link annotációig

Egy befejezett PDF hiperhivatkozása nem része az oldaltartalomnak. Ez egy különálló objektum, egy Link annotáció, amelyet az ISO 32000-1 12.5.6.5. szakasza ír le. Az annotációnak van egy /Rect-je, amely meghatározza a kattintható téglalapot az oldalon, és egy művelete, amely a téglalapra kattintáskor indul el. Egy külső hivatkozás esetében a művelet egy URI művelet: /S /URI, amelynek a célcíme a /URI karakterlánca. Az alatta lévő látható szöveg közönséges oldaltartalom; az annotáció a ráhelyezett láthatatlan forró zóna (hot zone)

A simítási útvonal pontosan ezt a modellt követi. Amikor egy futtatás egy Href-et hordoz, a HotPDF először megrajzolja a formázott szöveget, majd egy Link annotációt épít a futtatás doboza fölé. A nyilvános belépési pont ehhez az annotációhoz az AddURILink oldalmetódus, amely létrehozza a /Type /Annot /Subtype /Link objektumot egy /URI művelettel, és visszaadja az annotáció szótárat (dictionary). A téglalapja a futtatás mért doboza, lefordítva a rajzelem helyi koordinátáiból az oldal koordinátáiba. Az eredmény egy hivatkozás, amely pontosan a horgonyszövegre (anchor text) esik, és sehova máshova

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

Miért kell a találati doboznak a mért szélességekből származnia

Csábító elképzelni, hogy a hivatkozást úgy keressük meg, hogy a látható szövegét keressük az oldalon, és a téglalapot rajzoljuk köré annak, amit találunk. Ez nem működik, és ennek az oka alapvető abban, ahogyan a simított szöveget tárolják. A formázott futtatásokat beágyazott részhalmaz (subset) betűtípusokkal festik. Egy részhalmaz betűtípus átszámozza azokat a glifákat, amelyeket megtart, így az oldaltartalom-folyam (content stream) hexadecimális CID kódokat tartalmaz, nem pedig az eredeti karakterkódokat. Az oldalon lévő bájtok nem azok a betűk, amelyeket az ember olvas, és nem is kereshetők szövegként. A horgony feliratára (caption) történő keresés nem talál semmit, mert ez a felirat nem létezik szó szerinti szövegként a folyamon sehol

Az egyetlen megbízható horgony a téglalaphoz az a geometria, amelyet az elrendezési menet már létrehozott. Minden futtatás eltolását és mért szélességét a sor folyatása (flowing) közben számították ki, mielőtt bármely glifát átszámoztak volna, és ezek írják le, hol fog a szöveg fizikailag megjelenni. A HotPDF ezért a hivatkozás téglalapot egyenesen a futtatás lefektetett (laid-down) dobozából veszi, nem pedig bármilyen szövegkeresésből (text lookup). Mivel a mérés a renderelő betűtípust használta, a doboz a részhalmazokba rendezéstől (subsetting) függetlenül helyes. A geometria túléli a kódolást; a szöveg nem. Ez a teljes érv a mért szélességű pozicionálás mellett, és ezért állít elő egy olyan simító, amely a hivatkozásokat utólag, szövegkereséssel próbálja meg beilleszteni (retrofit), elsodródó vagy eltűnő találati zónákat

A simítás vezérlése az Ön kódjából

Egy olyan PDF esetében, amely már tartalmaz egy XFA csomagot, a belépési pont a FlattenLoadedXFA. Töltse be a dokumentumot, hívja meg a metódust, és mentse el az eredményt. Az Editable paraméter dönti el, mi történik az űrlapmezőkkel: adjon át True értéket, hogy kitölthető AcroForm widgetekként tartsa meg őket, vagy False értéket, hogy minden widgetet csak olvashatónak jelöljön, így a kimenet egy lefagyasztott rekord lesz. A rich-text rajzblokkok, azok formázott futtatásaival és link annotációival együtt így is, úgy is létrejönnek. A függvény visszaadja az általa kibocsátott widgetek számát

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;

Hívás után mindig olvassa el az XFAFlattenWarnings-t. A lista minden simítás elején törlődik, és felhalmoz egy sort minden olyan elemhez, amelyet a motor elutasított a rendereléskor: egy nem támogatott mezőfajta, egy rajzolt kép, amely nem volt dekódolható, egy exData blokk használható spanok nélkül. Ezek egyike sem vet fel kivételt, így egy üres figyelmeztetési lista a bizonyíték arra, hogy mindent leképeztek, a nem üres pedig pontosan megmondja, mely eredetiket kell megvizsgálnia. Amikor a nyers XFA-t XDP bájtokként tartja betöltött PDF helyett, a testvér ApplyXFAAsAcroForm metódus közvetlenül veszi ezeket a bájtokat, és ugyanazt a kódútvonalat és ugyanazt a figyelmeztetési viselkedést osztja meg. A kiegészítő AddXFAPacket metódus a másik irányba megy, beágyazva egy XFA csomagot egy Ön által épített dokumentumba

Az eredmény megerősítése egy olvasóban

Nyissa meg a simított fájlt az Acrobatban, vagy bármely más aktuális nézegetőben, és ellenőrizzen két dolgot. Először, a gazdag szöveg a stílusának sértetlen megőrzésével jelenik-e meg: a vastag futtatások vastagok, a színes futtatások hordozzák-e a színüket, és a spanok a megfelelő sorrendben ülnek-e a soron ahelyett, hogy átfednék egymást vagy lefutnának a dobozról. Másodszor, a hiperhivatkozások élnek-e. Vigye az egeret egy horgony fölé, és az állapotsornak meg kell mutatnia a célcímet; kattintson rá, és az URI műveletnek meg kell nyitnia azt. Használja a nézegető annotációvizsgálóját (annotation inspector) annak megerősítésére, hogy mindegyik valódi /Link annotáció, amelynek a /Rect-je átöleli a horgonyszöveget, és olyan tartalom felett ül, amely most már egyszerűen megfestett glifa az űrlapon renderelt XFA helyett. Ez a kombináció, a formázott statikus szöveg és a megfelelő téglalapokon lévő valódi Link annotációk teszik lehetővé, hogy a simított dokumentum túlélje azokat az XFA motorokat, amelyekre már nincs szüksége

Maguknak a mezőknek – a szövegdobozoknak, jelölőnégyzeteknek és választólistáknak, amelyek ezt a gazdag szöveget körülveszik – a simítását az XFA űrlapok AcroForm widgetekké történő simításáról szóló bemutatónk tárgyalja. A Link annotációk kézzel történő építésének és elhelyezésének tágabb történetéért – a simítási útvonal által generáltakon túl – lásd a munka PDF annotációkkal a HotPDF-ben részt. Mindkettő ugyanarra az annotáció- és űrlapmodellre épít, amelyet a Delphihez és C++Builderhez készült HotPDF komponenssel együtt szállítunk