PDFlibPas, izvorna knjižnica PDF za Delphi in C++Builder, ponuja dokumentu PDF dve ločeni mesti za samodejno vedenje: dejanja življenjskega cikla na ravni dokumenta, kot so WillClose, WillSave, DidSave, WillPrint in DidPrint, shranjena v slovarju /AA objekta Catalog, ter dejanja življenjskega cikla na ravni strani, Open in Close, shranjena v lastnem slovarju /AA vsakega objekta Page. Zamenjava teh dveh vsebnikov je najpogostejši razlog, da dejanje življenjskega cikla tiho ne naredi ničesar
Primeri, ki to motivirajo, so povsem vsakdanji. Finančna ekipa želi predlogo izpiska, ki ob dejanskem začetku tiskanja doda časovni žig tiskanja in zabeleži, kdo ga je natisnil, ne pa takrat, ko se datoteka samo odpre. Potek dela z veliko obrazci mora vrednosti polj samodejno poslati strežniku, preden bralčev odjemalec PDF sme zapreti okno, tako da zaprti zavihek nikoli ne pomeni izgubljenega urejanja. Večstransko poročilo želi pasico, povezano z določeno stranjo, ki se prikaže samo, ko je ta stran na zaslonu. PDF za takšno vedenje dejansko ponuja še tretjo raven pod dokumentom in stranjo: dejanja, pripeta posameznemu obrazcu ali lastnemu vnosu /A povezave, kar je tema spremljevalnega članka o interaktivnih dejanjih obrazcev in JavaScriptu — ta članek pa ostaja pri zgornjih dveh ravneh: celotnem dokumentu in posamezni strani
Kateri sprožilci so v Catalogu dokumenta v /AA?
V slovarju /AA objekta Catalog je pet sprožilcev in vsak se sproži ob dogodku, ki vpliva na celoten dokument, ne na posamezno stran. ISO 32000-1 §12.6.3 (Trigger Events) navaja ključe na ravni dokumenta WC, WS, DS, WP in DP — dobesedna dvobesedna imena, zapisana v slovarju /AA — za WillClose, WillSave, DidSave, WillPrint in DidPrint, po vrsti, PDFlibPas pa ta nabor natančno zrcali v enumeraciji TPDFlibDocumentActionTrigger: datWillClose, datWillSave, datDidSave, datWillPrint, datDidPrint. SetDocumentAction je enotna vstopna točka za pripenjanje katerega koli od petih sprožilcev, parameter ActionKind, ki ga sprejme, pa je ena od desetih konstant PDF_ACTION_BUILDER_*, ki si jih delijo vsi klici gradnika dejanj v knjižnici, od navadnega URI-ja do skripta ali skoka na cilj. Kaj dejanje GoTo, remote-file, embedded-file ali Launch dejansko naredi po sprožitvi, je drugo vprašanje kot to, kam je pripeto, in to je tema spremljevalnega članka o dejanjih GoTo, remote, embedded in launch — ta članek se ukvarja z vsebnikom, Catalogom ali Pageom, ne z vrsto dejanja
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.AddStandardFont(4);
Lib.DrawText(40, 700, 'Quarterly statement');
Lib.SetDocumentAction(datWillSave, PDF_ACTION_BUILDER_WEB,
'https://example.com/audit/will-save', '', 0, 0);
Lib.SetDocumentAction(datWillClose, PDF_ACTION_BUILDER_SUBMIT,
'https://example.com/forms/submit', 'CustomerName;OrderTotal', 0, 0);
Lib.SaveToFile('statement.pdf');
finally
Lib.Free;
end;
end;
Kako se sprožilec na ravni strani razlikuje od tistega na ravni dokumenta?
Sprožilec na ravni strani se sproži samo za posamezni objekt Page, ki mu je pripet, PDFlibPas pa ga shrani v lasten slovar /AA te strani in ne v slovar objekta Catalog. Obstajata samo dva sprožilca strani, Open in Close, ki ustrezata ključema O in C, ki ju ISO 32000-1 določa za slovar dodatnih dejanj strani, PDFlibPas pa ju prek SetPageAction izpostavi kot patOpen in patClose. To dejanje se pripne tisti strani, ki je trenutno izbrana prek SelectPage — podrobnost, ki je pomembna, ko prvič prehajate skozi dokument in pričakujete, da bo en klic veljal povsod, saj se to nikoli ne zgodi. Pripenjanje katere koli vrste sprožilca prav tako zviša najmanjšo različico PDF-ja v datoteki, oba vsebnika pa zahtevata različni meji: PDFlibPas zviša dokument najmanj na PDF 1.4, ko prvič zapiše vnos Catalog /AA, in najmanj na PDF 1.5, ko prvič zapiše vnos Page /AA, ne glede na to, katera vrsta dejanja je v njem. To je zahteva na ravni vsebnika, dodana zahtevam samega dejanja, zato osnovno dejanje URI, ki bi samostojno potrebovalo le PDF 1.1, vseeno dvigne celotno datoteko na PDF 1.5, ko je ovito v sprožilec ob odprtju strani
Lib.SelectPage(3);
Lib.SetPageAction(patOpen, PDF_ACTION_BUILDER_JAVASCRIPT,
'app.alert("Section 3: internal review only");', '', 0, 0);
Lib.SetPageAction(patClose, PDF_ACTION_BUILDER_WEB,
'https://example.com/analytics/page-3-closed', '', 0, 0);
Branje in odstranjevanje dejanj življenjskega cikla
GetDocumentActionInfo in GetPageActionInfo vrneta zapis TPDFlibActionInfo, polje Kind pa ima vrednost akNone, kadar temu sprožilcu ni pripeto nič, zato pred uporabo katerega koli drugega polja zapisa preverite Kind — URI, JavaScript, FileName in preostala polja so smiselna samo za tisto vrsto dejanja, ki jo Kind dejansko poroča, saj se ista oblika zapisa uporablja za vse vrste dejanj, ki jih lahko izdela gradnik. RemoveDocumentAction in RemovePageAction vsak odstrani en sam sprožilec ter vrne 1, če je našel nekaj za odstranitev, oziroma 0, če je bil sprožilec že prazen; ko je odstranjeni vnos zadnji v slovarju /AA, PDFlibPas izbriše tudi zdaj prazni /AA, namesto da bi za seboj pustil viseč in nesmiseln vsebnik na objektu Catalog ali Page
var
Info: TPDFlibActionInfo;
begin
Info := Lib.GetDocumentActionInfo(datWillSave);
if Info.Kind = akURI then
WriteLn('WillSave calls out to: ', string(Info.URI));
if Lib.RemoveDocumentAction(datWillSave) = 1 then
Lib.SetDocumentAction(datWillSave, PDF_ACTION_BUILDER_WEB,
'https://example.com/audit/will-save-v2', '', 0, 0);
end;
Ali PDF/A sploh dovoljuje dejanja življenjskega cikla?
Ne. Skladnost s PDF/A zavrne celoten vsebnik dodatnih dejanj, ne le vrst dejanj, ki zvenijo tvegano, saj ISO 19005 omejuje model interaktivnih dejanj PDF-ja na predpostavki, da mora arhivska datoteka tudi čez desetletja prikazati enak rezultat, ne da bi bila odvisna od skriptnega mehanizma ali omrežne povezave, ki takrat morda ne bo več obstajala. SetLifecycleAction, skupni gradnik za SetDocumentAction in SetPageAction, preveri PDFAMode, še preden pogleda ActionKind, zato je dejanje URI, ki samo odpre spletno stran podjetja, ali dejanje Named, ki pomeni le prehod na naslednjo stran, ujeto v isto mrežo kot nevarno dejanje — blokirano je tudi nekaj, česar varnostni pregledovalec običajno ne bi označil, ker je omejitev strukturna in ne velja od primera do primera. Praktična nevarnost je tiha zavrnitev: SetDocumentAction in SetPageAction vrneta 0, ne da bi sprožila izjemo, zato klicno mesto, ki nikoli ne preveri povratne vrednosti, pošlje dokument brez sprožilca, ki bi ga moral vsebovati
Lib.SetPDFAMode(2); // PDF/A-1b
if Lib.SetDocumentAction(datWillClose, PDF_ACTION_BUILDER_NAMED,
'', '', 0, 0) = 0 then
// rejected: PDF/A-1b forbids Catalog /AA, even a plain Named action
WriteLn('lifecycle action not attached');
Upoštevati velja eno asimetrijo. RemoveDocumentAction in RemovePageAction nikoli ne preverjata PDFAMode, zato nalaganje datoteke, ki že vsebuje neskladna dejanja življenjskega cikla, in njihovo odstranjevanje na poti do shranjevanja, skladnega s PDF/A, deluje natanko pričakovano — preverjanje načina skladnosti velja samo za zapisovalno pot, torej za pripenjanje novega sprožilca
Kam sodi tiskanje ob odprtju brez sprožilca WillOpen?
Slovar /AA objekta Catalog po zasnovi sploh nima vnosa WillOpen — dokumentna raven /AA v standardu ISO 32000-1 določa natanko pet ključev, WillClose, WillSave, DidSave, WillPrint in DidPrint, noben od njih pa se ne sproži samo zato, ker je bila datoteka odprta. Kavelj ob odprtju je v ločenem vnosu Catalog, /OpenAction, ki ga PDFlibPas izpostavlja prek lastne družine klicev, med drugim SetOpenActionJavaScript, SetOpenActionDestination in SetOpenActionNamedDestination, pri čemer se noben od njih ne dotakne slovarja /AA ali enumeracije TPDFlibDocumentActionTrigger. Mehanizma se vendarle dopolnjujeta in prav to običajno potrebuje predloga za tiskanje ob odprtju: predlogo sestavite tako, da njen /OpenAction začne tiskalno opravilo, običajno z dejanjem JavaScript, ki pokliče ukaz za tiskanje v pregledovalniku, samo tiskanje pa sprožilcema WillPrint in DidPrint omogoči, da nekaj izvedeta — časovni žig se zapiše, preden se strani začnejo tiskati, revizijski vnos pa, ko je delo končano
Kako zanesljivi so ti sprožilci v različnih pregledovalnikih PDF-ja?
Ne izvede jih vsak pregledovalnik, niti zunaj PDF/A, zato dejanje življenjskega cikla obravnavajte kot zahtevo in ne kot zagotovilo. Acrobat in večina popolnih namiznih bralnikov zvesto izvedeta celoten nabor, vendar velik del dejanske uporabe PDF-ja sploh ne doseže slovarja dodatnih dejanj: pregledovalniki, vdelani v brskalnike, večina mobilnih bralnikov ter skoraj vsi strežniški cevovodi za upodabljanje ali pridobivanje besedila slovar /AA bodisi v celoti prezrejo bodisi upoštevajo le njegov ozek del, pri čemer se WillPrint in DidPrint običajno odrežeta najslabše, ker brezglava pretvorba nima tiskalnega postopka, na katerega bi ju lahko pripela. Če je dejanje obrazca za pošiljanje ob WillClose edina pot za zajem podatkov obrazca, to ni zanesljiva pot — dodajte izrecni gumb za pošiljanje, samodejni sprožilec pa obravnavajte kot priročnost za bralnike, ki ga podpirajo
Sprožilci dokumenta, strani in polja so tri ravni iste osnovne mehanike slovarjev dejanj, in ko je vsebnik jasen, je treba samo izbrati pravo konstanto ActionKind ter preveriti povratno kodo. Ti sprožilci življenjskega cikla in širši API gradnika dejanj, ki se ga ta članek dotika, so del standardne knjižnice PDFlibPas Delphi PDF, popoln sklic na sprožilce in vrste dejanj pa je v dokumentaciji izdelka