PDFlibPas, gimtoji Delphi ir C++Builder PDF biblioteka, PDF dokumente suteikia dvi atskiras vietas automatiniam elgesiui prijungti: dokumento lygio gyvavimo ciklo veiksmus, tokius kaip WillClose, WillSave, DidSave, WillPrint ir DidPrint, saugomus Catalog /AA žodyne, ir puslapio lygio gyvavimo ciklo veiksmus – Open ir Close – saugomus kiekvieno Page objekto nuosavame /AA žodyne. Supainiojus šiuos du konteinerius, gyvavimo ciklo veiksmas dažniausiai tyliai nieko neatlieka
Praktiniai atvejai yra įprasti. Finansų komanda nori ataskaitos šablono, kuris įrašo spausdinimo laiko žymą ir užregistruoja, kas ją išspausdino, vos prasidėjus tikram spausdinimui, o ne vien atidarius failą. Formomis paremtai darbo eigai reikia automatiškai nusiųsti laukų reikšmes į serverį prieš PDF skaitytuvui leidžiant uždaryti langą, kad užvertas skirtukas niekada nereikštų prarasto taisymo. Kelių puslapių ataskaitoje gali reikėti konkrečiam puslapiui skirto skydelio, rodomo tik tada, kai tas puslapis yra ekrane. PDF tokio elgesio atveju iš tikrųjų siūlo trečią lygį žemiau dokumento ir puslapio – veiksmus, prijungtus prie atskiro formos lauko arba nuorodos nuosavo /A įrašo, aptariamus gretimame straipsnyje apie interaktyvius formų veiksmus ir JavaScript – tačiau šiame straipsnyje nagrinėjami du aukštesni lygiai: visas dokumentas ir vienas puslapis
Kokie sužadinimo įvykiai gyvena dokumento Catalog /AA?
Catalog /AA žodyne yra penki sužadinimo įvykiai, ir kiekvienas jų įvyksta dėl įvykio, veikiančio visą dokumentą, o ne vieną puslapį. ISO 32000-1 §12.6.3 (Trigger Events) dokumento lygio raktus nurodo kaip WC, WS, DS, WP ir DP – pažodinius dviejų raidžių pavadinimus, įrašomus į /AA žodyną – atitinkamai WillClose, WillSave, DidSave, WillPrint ir DidPrint, o PDFlibPas tiksliai atkartoja šį rinkinį TPDFlibDocumentActionTrigger išvardijime: datWillClose, datWillSave, datDidSave, datWillPrint, datDidPrint. SetDocumentAction yra vienintelis įėjimo taškas, prijungiantis bet kurį iš penkių veiksmų, o jo parametras ActionKind yra vienas iš dešimties PDF_ACTION_BUILDER_* konstantų, bendrų visiems bibliotekos veiksmų kūrimo iškvietimams – nuo paprasto URI iki scenarijaus ar perėjimo į paskirties vietą. Ką sužadintas GoTo, nuotolinio failo, įterpto failo ar Launch veiksmas iš tikrųjų daro, yra kitas klausimas nei tai, kur jis prijungiamas, ir jis nagrinėjamas gretimame straipsnyje apie GoTo, nuotolinius, įterptus ir paleidimo veiksmus – čia liekame ties konteinerio klausimu, Catalog ar Page, o ne veiksmo tipo klausimu
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;
Kuo puslapio lygio sužadinimo įvykis skiriasi nuo dokumento lygio?
Puslapio lygio sužadinimo įvykis įvyksta tik vienam Page objektui, prie kurio jis prijungtas, o PDFlibPas jį saugo paties puslapio /AA žodyne, o ne Catalog žodyne. Yra tik du puslapio sužadinimo įvykiai – Open ir Close, atitinkantys O ir C raktus, kuriuos ISO 32000-1 apibrėžia puslapio papildomų veiksmų žodynui, o PDFlibPas juos pateikia kaip patOpen ir patClose per SetPageAction, kuri prijungia veiksmą prie tuo metu pasirinkto puslapio per SelectPage – tai svarbi detalė, kai pirmą kartą cikle pereinate per dokumentą tikėdamiesi, kad vienas iškvietimas bus pritaikytas visur, nes taip niekada neįvyksta. Prijungus bet kurio tipo sužadinimo įvykį taip pat pakeliama minimali failo PDF versija, o šiems dviem konteineriams reikia skirtingų lygių: pirmą kartą įrašydamas Catalog /AA įrašą PDFlibPas pakelia dokumentą bent iki PDF 1.4, o pirmą kartą įrašydamas Page /AA įrašą – bent iki PDF 1.5, nesvarbu, koks veiksmo tipas yra viduje. Tai konteinerio lygio reikalavimas, papildantis tai, ko pačiam veiksmui reikia, todėl paprastas URI veiksmas, veikdamas vienas, reikalaujantis tik PDF 1.1, vis tiek pakelia visą failą iki PDF 1.5, kai įdedamas į puslapio atidarymo sužadinimo įvykį
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);
Gyvavimo ciklo veiksmų skaitymas ir šalinimas
GetDocumentActionInfo ir GetPageActionInfo abu grąžina TPDFlibActionInfo įrašą, o laukas Kind grąžinamas kaip akNone, kai tam sužadinimo įvykiui niekas neprijungta, todėl prieš pasitikėdami bet kuriuo kitu įrašo lauku patikrinkite Kind – URI, JavaScript, FileName ir kiti laukai yra prasmingi tik tam vieninteliam veiksmo tipui, kurį nurodo Kind, nes ta pati įrašo forma pakartotinai naudojama kiekvienam kūrėjo galimam veiksmo tipui. RemoveDocumentAction ir RemovePageAction išvalo po vieną sužadinimo įvykį ir grąžina 1, kai randa ką pašalinti, arba 0, kai sužadinimo įvykis jau tuščias; jei pašalintas įrašas buvo paskutinis /AA žodyne, PDFlibPas ištrina ir dabar tuščią patį /AA, nepalikdamas beprasmiško kabančio konteinerio Catalog arba puslapyje
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;
Ar PDF/A apskritai leidžia gyvavimo ciklo veiksmus?
Ne. PDF/A atitiktis atmeta visą papildomų veiksmų konteinerį, o ne tik rizikingai skambančius veiksmų tipus, nes ISO 19005 riboja PDF interaktyvių veiksmų modelį remdamasis prielaida, kad archyvinis failas po kelių dešimtmečių turi būti atvaizduojamas taip pat, nepriklausydamas nuo scenarijų variklio ar tinklo ryšio, kurio tuo metu gali nebūti. SetLifecycleAction, bendras kūrėjas, naudojamas už SetDocumentAction ir SetPageAction, patikrina PDFAMode dar prieš nagrinėdamas ActionKind, todėl URI veiksmas, tik atveriantis įmonės tinklalapį, arba Named veiksmas, reiškiantis tik perėjimą į kitą puslapį, patenka į tą patį tinklą kaip ir pavojingas veiksmas – užblokuojamas net tas, kurio saugumo tikrintojas paprastai nepažymėtų, nes apribojimas yra struktūrinis, o ne taikomas kiekvienu atveju. Praktinis pavojus tas, kad atmetimas yra tylus: SetDocumentAction ir SetPageAction abi grąžina 0, nesukeldamos išimties, todėl iškvietimo vieta, kuri niekada netikrina grąžinamos reikšmės, gali išsiųsti dokumentą be turėjusio būti jame sužadinimo įvykio
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');
Verta prisiminti vieną asimetriją. RemoveDocumentAction ir RemovePageAction niekada netikrina PDFAMode, todėl įkėlus failą, kuriame jau yra neatitinkančių reikalavimų gyvavimo ciklo veiksmų, ir pakeliui į PDF/A atitinkantį išsaugojimą juos pašalinus, viskas veikia tiksliai taip, kaip tikimasi – tik rašymo kelias, naujo sužadinimo įvykio prijungimas, priklauso nuo atitikties režimo
Kaip veikia spausdinimas atidarant failą, jei nėra WillOpen sužadinimo įvykio?
Catalog /AA žodyne tyčia nėra WillOpen įrašo – dokumento lygio /AA ISO 32000-1 apibrėžia tiksliai penkis raktus: WillClose, WillSave, DidSave, WillPrint ir DidPrint, ir nė vienas jų nesuveikia vien dėl to, kad failas buvo atidarytas. Atidarymo momento kabliukas yra atskirame Catalog įraše /OpenAction, kurį PDFlibPas pateikia per atskirą iškvietimų šeimą, tarp jų SetOpenActionJavaScript, SetOpenActionDestination ir SetOpenActionNamedDestination; nė vienas iš jų neliečia /AA žodyno ar TPDFlibDocumentActionTrigger išvardijimo. Vis dėlto abu mechanizmai dera, ir dažniausiai būtent to reikia šablonui, kuris spausdina atidarymo metu: sukurkite šabloną taip, kad jo /OpenAction pradėtų spausdinimo užduotį, paprastai JavaScript veiksmu, iškviečiančiu paties skaitytuvo spausdinimo komandą, o būtent spausdinimas suteiks WillPrint ir DidPrint įvykiams ką vykdyti – laiko žymą įrašyti prieš puslapiams patenkant į eilę ir audito įrašą sukurti jiems baigus spausdinti
Kiek patikimi šie sužadinimo įvykiai skirtinguose PDF skaitytuvuose?
Ne kiekvienas skaitytuvas juos vykdo, net ir ne PDF/A režimu, todėl gyvavimo ciklo veiksmą laikykite prašymu, o ne garantija. Acrobat ir dauguma visaverčių darbalaukio skaitytuvų patikimai vykdo visą rinkinį, tačiau didelė dalis tikro PDF naudojimo apskritai nepaliečia papildomų veiksmų žodyno: naršyklėse įterpti skaitytuvai, dauguma mobiliųjų skaitytuvų ir beveik visi serverio atvaizdavimo ar teksto išgavimo procesai visiškai ignoruoja /AA arba palaiko tik siaurą jo dalį, o WillPrint ir DidPrint paprastai veikia prasčiausiai, nes bekūnei konversijai nėra spausdinimo operacijos, prie kurios būtų galima juos prijungti. Jei WillClose formos pateikimo veiksmas yra vienintelis kelias surinkti formos duomenis, tai nėra patikimas kelias – susiekite jį su aiškiu pateikimo mygtuku ir automatinį sužadinimo įvykį laikykite patogumu skaitytuvams, kurie jį palaiko
Dokumento, puslapio ir lauko sužadinimo įvykiai yra trys tos pačios pagrindinės veiksmų žodyno sistemos lygiai, o išsiaiškinus konteinerį belieka pasirinkti tinkamą ActionKind konstantą ir patikrinti grąžinamą kodą. Šie gyvavimo ciklo sužadinimo įvykiai ir platesnė šiame straipsnyje paliesta veiksmų kūrimo API pateikiami kartu su standartine PDFlibPas Delphi PDF biblioteka, o išsami sužadinimo įvykių ir veiksmų tipų nuoroda pateikiama produkto dokumentacijoje