Tekninen artikkeli

PDF:n elinkaaritoiminnot: Catalog /AA vastaan Page /AA Delphissä

PDFlibPas, natiivi Delphi- ja C++Builder-PDF-kirjasto, antaa PDF-asiakirjalle kaksi erillistä paikkaa ripustaa automaattista käytöstä: asiakirjatason elinkaaritoiminnot kuten WillClose, WillSave, DidSave, WillPrint ja DidPrint, tallennettuna Catalogin /AA-sanakirjaan, ja sivutason elinkaaritoiminnot — Open ja Close — tallennettuna sen sijaan jokaisen Page-olion omaan /AA-sanakirjaan. Näiden kahden säiliön sekoittaminen on yleisin yksittäinen tapa, jolla elinkaaritoiminto hiljaa ei tee mitään

Motivoivat tapaukset ovat tavallisia. Talousosaton tiimi haluaa tiliotemallin, joka leimaa tulostusaikaleiman ja kirjaa, kuka tulosti sen, siinä hetkessä, kun tulostus todella alkaa, ei kun tiedosto pelkästään avautuu. Lomakepainotteinen työnkulku tarvitsee kenttäarvot työnnettynä palvelimelle automaattisesti ennen kuin lukijan PDF-asiakkaan sallitaan sulkea ikkuna, joten suljettu välilehti ei koskaan tarkoita menetettyä muokkausta. Monisivuinen raportti haluaa sivukohtaisen bannerin, joka ilmestyy vain, kun tuo sivu on näytöllä. PDF todella tarjoaa kolmannen tason asiakirjan ja sivun alapuolella tällaiselle käytökselle — toiminnot, jotka on kiinnitetty yksittäisen lomakekentän tai linkin omaan /A-merkintään, aihe artikkelissa rinnakkaisartikkeli interaktiivisista lomaketoiminnoista ja JavaScriptistä — mutta tämä artikkeli pysyy niissä kahdessa tasossa sen yläpuolella: koko asiakirja ja yksi sivu

Mitkä laukaisimet asuvat asiakirjan Catalogin /AA:ssa?

Viisi laukaisinta asuu Catalogin /AA-sanakirjassa, ja jokainen niistä laukeaa tapahtumalle, joka vaikuttaa koko asiakirjaan, ei yhteen sivuun. ISO 32000-1 §12.6.3 (Trigger Events) listaa asiakirjatason avaimet WC, WS, DS, WP ja DP — kirjaimelliset kaksikirjaimiset nimet, jotka kirjoitetaan /AA-sanakirjaan — WillClose:lle, WillSave:lle, DidSave:lle, WillPrint:lle ja DidPrint:lle vastaavasti, ja PDFlibPas peilaa tuon joukon täsmälleen TPDFlibDocumentActionTrigger-enumissa: datWillClose, datWillSave, datDidSave, datWillPrint, datDidPrint. SetDocumentAction on se yksi sisääntulopiste, joka kiinnittää minkä tahansa näistä viidestä, ja ActionKind-parametri, jonka se ottaa, on yksi kymmenestä PDF_ACTION_BUILDER_*-vakiosta, jotka jaetaan jokaisen toimintorakentajakutsun kesken kirjastossa, tavallisesta URI:sta skriptiin tai kohteeseen hyppäämiseen. Mitä GoTo-, etätiedosto-, upotettu tiedosto- tai Launch-toiminto todella tekee laukaistuaan, on eri kysymys kuin missä se kiinnitetään, ja se on aihe artikkelissa rinnakkaisartikkeli GoTo-, etä-, upotetuista ja käynnistystoiminnoista — tämä pysyy säiliökysymyksessä, Catalog vai Page, eikä toimintotyyppikysymyksessä

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;

Miten sivutason laukaisin eroaa asiakirjatasoisesta?

Sivutason laukaisin laukeaa vain sille yhdelle Page-oliolle, johon se on kiinnitetty, ja PDFlibPas tallentaa sen tuon sivun omaan /AA-sanakirjaan Catalogin sijaan. Sivulaukaisimia on vain kaksi, Open ja Close, vastaten O- ja C-avaimia, jotka ISO 32000-1 määrittelee sivun lisätoimintojen sanakirjalle, ja PDFlibPas paljastaa ne patOpen- ja patClose-arvoina SetPageAction:n kautta, joka kiinnittyy siihen sivuun, joka on juuri nyt valittu SelectPage:n kautta — yksityiskohta, joka on merkityksellinen ensimmäisellä kerralla, kun silmukoit asiakirjan yli odottaen yhden kutsun soveltuvan kaikkialle, koska se ei koskaan tee niin. Kummankin laukaisintyypin kiinnittäminen myös nostaa tiedoston minimi-PDF-versiota, ja nämä kaksi säiliötä pyytävät eri lattioita: PDFlibPas nostaa asiakirjan vähintään PDF 1.4:ään ensimmäisellä kerralla, kun se kirjoittaa Catalog /AA -merkinnän, ja vähintään PDF 1.5:een ensimmäisellä kerralla, kun se kirjoittaa Page /AA -merkinnän, riippumatta siitä, mikä toimintotyyppi istuu sisällä. Se on säiliötason vaatimus, joka on kerrostettu sen päälle, mitä itse toiminto tarvitsee itsessään, joten paljas URI-toiminto, joka yksinään vaatisi vain PDF 1.1:n, silti vetää koko tiedoston ylös PDF 1.5:een heti, kun se on käärittynä sivun avauslaukaisimeen

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

Elinkaaritoimintojen lukeminen ja poistaminen

GetDocumentActionInfo ja GetPageActionInfo palauttavat molemmat TPDFlibActionInfo-tietueen, ja Kind-kenttä palautuu akNone-arvolla aina, kun tuolla laukaisimella ei ole mitään kiinnitettynä, joten tarkista Kind ennen kuin luotat mihinkään muuhun kenttään tietueessa — URI, JavaScript, FileName ja loput ovat merkityksellisiä vain sille yhdelle toimintotyypille, jonka Kind todella raportoi, koska sama tietuemuoto käytetään uudelleen jokaiselle toimintotyypille, jonka rakentaja voi tuottaa. RemoveDocumentAction ja RemovePageAction tyhjentävät kumpikin yhden laukaisimen ja raportoivat 1:n, kun ne löysivät jotain poistettavaa, 0:n, kun laukaisin oli jo tyhjä; kun poistettu merkintä oli viimeinen jäljellä oleva /AA-sanakirjassa, PDFlibPas poistaa nyt tyhjän /AA:n itsensä sen sijaan, että jättäisi roikkuvan, merkityksettömän säiliön jälkeensä Catalogiin tai sivulle

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;

Sallitaanko elinkaaritoiminnot PDF/A:ssa lainkaan?

Ei. PDF/A-yhdenmukaisuus hylkää koko lisätoimintojen säiliön, ei vain vaarallisen kuuloisia toimintotyyppejä, koska ISO 19005 rajoittaa PDF:n interaktiivista toimintomallia olettaen, että arkistointitiedoston on renderöitävä samalla tavalla vuosikymmenten päästä, riippumatta skriptimoottorista tai verkkoyhteydestä, joka ei ehkä ole olemassa siihen mennessä. SetLifecycleAction, jaettu rakentaja sekä SetDocumentAction:n että SetPageAction:n takana, tarkistaa PDFAMode:n ennen kuin se koskaan katsoo ActionKind:ia, joten URI-toiminto, joka vain avaa yrityksen verkkosivun, tai Named-toiminto, joka tarkoittaa vain siirry seuraavalle sivulle, jää samaan verkkoon kuin vaarallinen — mikään, mitä tietoturvakatselmoija ei normaalisti merkitsisi, estetään joka tapauksessa, koska rajoitus on rakenteellinen eikä tapauskohtainen. Käytännön vaara on, että hylkäys on hiljainen: SetDocumentAction ja SetPageAction molemmat palauttavat 0:n nostamatta poikkeusta, joten kutsupaikka, joka ei koskaan tarkista paluuarvoa, toimittaa asiakirjan, josta hiljaa puuttuu laukaisin, jonka sen piti kantaa

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');

Yksi epäsymmetria kannattaa pitää mielessä. RemoveDocumentAction ja RemovePageAction eivät koskaan tarkista PDFAMode:ia, joten tiedoston lataaminen, joka jo kantaa yhdenmukaisuuden vastaisia elinkaaritoimintoja, ja niiden poistaminen matkalla kohti PDF/A-yhdenmukaista tallennusta toimii täsmälleen odotetusti — vain kirjoituspolku, uuden laukaisimen kiinnittäminen, on porrastettu yhdenmukaisuustilan mukaan

Mihin tulosta-avattaessa sopii ilman WillOpen-laukaisinta?

Catalog /AA -sanakirjassa ei ole lainkaan WillOpen-merkintää, suunnitellusti — asiakirjatason /AA ISO 32000-1:ssä määrittelee täsmälleen viisi avainta, WillClose, WillSave, DidSave, WillPrint ja DidPrint, eikä mikään tuossa listassa laukea puhtaasti siksi, että tiedosto avattiin. Avaushetken koukku asuu erillisessä Catalog-merkinnässä, /OpenAction, jonka PDFlibPas paljastaa oman kutsuperheensä kautta, SetOpenActionJavaScript, SetOpenActionDestination ja SetOpenActionNamedDestination niiden joukossa, mikään niistä ei kosketa /AA-sanakirjaa tai TPDFlibDocumentActionTrigger-enumia lainkaan. Nämä kaksi mekanismia kuitenkin yhdistyvät, ja se on yleensä se, mitä tulosta-avattaessa-malli todella tarvitsee: rakenna malli niin, että sen /OpenAction käynnistää tulostustyön, tyypillisesti JavaScript-toiminto, joka kutsuu katseluohjelman omaa tulostuskomentoa, ja itse tulostus on se, mikä antaa WillPrint:lle ja DidPrint:lle jotain, mitä vasten ajaa — aikaleima leimattuna sisään ennen kuin sivut menevät jonoon, tarkastusmerkintä kirjoitettuna kerran, kun ne ovat valmiit

Kuinka luotettavia nämä laukaisimet ovat PDF-katseluohjelmien yli?

Ei jokainen katseluohjelma aja niitä, edes PDF/A:n ulkopuolella, joten kohtele elinkaaritoimintoa pyyntönä eikä takuuna. Acrobat ja useimmat täydet työpöytälukijat suorittavat koko joukon uskollisesti, mutta suuri osa todellisesta PDF-kulutuksesta ei koskaan kosketa lisätoimintojen sanakirjaa lainkaan: selaimeen upotetut katseluohjelmat, useimmat mobiililukijat, ja lähes jokainen palvelinpuolen renderöinti- tai tekstinpoimintaputki joko sivuuttaa /AA:n suoraan tai kunnioittaa vain kapeaa siivua siitä, WillPrint:n ja DidPrint:n yleensä pärjätessä huonoiten, koska pääsäätämättömällä konversiolla ei ole tulostustoimintoa, johon ne voisivat koukuttaa. Jos WillClose-lähetä-lomake-toiminto on ainoa polku, joka vangitsee lomakedatan, se ei ole luotettava polku — pariuta se nimenomaisen lähetä-painikkeen kanssa, ja kohtele automaattista laukaisinta mukavuutena lukijoille, jotka sattuvat tukemaan sitä

Asiakirja-, sivu- ja kenttälaukaisimet ovat kolme tasoa samaa taustalla olevaa toimintosanakirjakoneistoa, ja heti kun säiliö on selvä, loppu on oikean ActionKind-vakion valitsemista ja paluukoodin tarkistamista. Nämä elinkaarilaukaisimet, yhdessä tämän artikkelin koskettaman laajemman toimintorakentaja-API:n kanssa, toimitetaan osana vakiomuotoista PDFlibPas Delphi-PDF-kirjastoa, täyden laukaisin- ja toimintotyyppiviitteen kanssa tuotedokumentaatiossa