Tehnični članak

Plasti PDF v Delphi: Izbirne skupine vsebin (OCG)

Geodet odpre načrt lokacije in želi, da so plasti s konturami skrite, medtem ko instalacije ostanejo vidne. Ocenjevalec želi, da so rdeče korekturne pripombe vidne na zaslonu in odstranjene iz natisnjenega dokumenta. Predstavitveni list izdelka se pošlje v treh jezikih iz ene datoteke, bralec pa izbere, kateri jezik bo prikazan. Vsi trije primeri predstavljajo isto funkcijo PDF, plošča, ki jih upravlja v programu Acrobat, pa se imenuje Plasti (Layers). Funkcija pod to ploščo je izbirna vsebina (optional content) in prav to omogoča, da ena sama stran vsebuje več neodvisnih vizualnih slojev, ki jih pregledovalnik vklopi ali izklopi

Izbirna vsebina je določena v standardu ISO 32000-1 §8.11. Enota vidnosti je izbirna skupina vsebin (optional content group), OCG, slovar vrste /OCG, ki nosi ime. Označena vsebina na strani je povezana s skupino, pregledovalnik pa se odloči, ali je ta skupina trenutno prikazana. Sorodna struktura, slovar članstva izbirne vsebine ali OCMD (optional content membership dictionary), omogoča, da je vidnost odvisna od logične kombinacije več skupin, toda vsakodnevni primer je ena sama poimenovana skupina, ki predstavlja posamezno plast. Dokument povezuje celoten mehanizem prek enega vnosa v katalogu, /OCProperties, ki je opisan v nadaljevanju

Kaj mora katalog vsebovati

Skupina OCG je sama po sebi inertna. Da bi pregledovalnik lahko izpisal plast in si zapomnil njeno stanje, katalog dokumenta potrebuje slovar /OCProperties, pri čemer §8.11.4 natančno določa, kaj gre vanj. V njem je matrika /OCGs, ki poimenuje vsako skupino v datoteki, in vnos /D, ki vsebuje privzeto konfiguracijo. Privzeta konfiguracija je del, ki ga bralnik uveljavi ob prvem odprtju datoteke. Beleži, katere skupine se začnejo kot vklopljene in katere kot izklopljene, kateri vnosi so zaklenjeni pred uporabniškim preklapljanjem ter, prek matrike /Order, kako so imena plasti razporejena in gnezdena v plošči

Zgradba neobveznih skupin vsebine PDF za PDF Library for Delphi: vnos OCProperties kataloga našteje vsako plast OCG in njeno privzeto nastavitev z začetnimi stanji, zaklepi in vrstnim redom na plošči, označena vsebina strani pa veže risbe na skupine
Plasti postanejo naslovljive le skozi katalog: OCProperties našteje vsak OCG, privzeta nastavitev pa zabeleži začetna stanja, zaklepe in vrstni red plošče

Praktična posledica tega je, da ustvarjanje plasti nikoli ni povsem lokalno dejanje. Skupino je treba izrisati na strani, poleg tega pa jo je treba registrirati v strukturi na ravni kataloga, ki prej ni obstajala. PDF Library for Delphi stori oboje namesto vas. Prvi klic, ki ustvari skupino, doda vnos /OCProperties v katalog in zaseje privzeto konfiguracijo, so plast hkrati izrisana in uvrščena na seznam brez ločenega vodenja evidenc z vaše strani

Zakaj lahko način skladnosti to funkcijo zavrne

Preden se zažene kakršna koli koda za plasti, cilj skladnosti dokumenta določi, ali je izbirna vsebina sploh dovoljena. PDF/A-1, arhivski profil, opredeljen v ISO 19005-1, v §6.1.13 popolnoma prepoveduje vnos /OCProperties. Utemeljitev ustreza namenu formata. Arhivska datoteka se mora za vsakega bralca v daljni prihodnosti izrisati popolnoma enako, vsebina, katere vidnost lahko pregledovalnik spreminja, pa je vsebina, katere videz ni fiksen, zato profil prepoveduje to strukturo, namesto da bi dovolil dvoumen arhiv. PDF/A-2 in PDF/A-3, opredeljena v ISO 19005-2 in ISO 19005-3, imata v svojih §6.9 nasprotno stališče in dovoljujeta izbirno vsebino s pravili o privzeti vidnosti

Ta razlika se neposredno pokaže v API. Ko je dokument v načinu PDF/A-1, funkcija NewOptionalContentGroup zavrne ustvarjanje skupine in vrne nič, saj bi izpolnitev zahteve ustvarila datoteko, ki ne bi ustrezala svoji deklarirani skladnosti. V načinu PDF/A-2 ali PDF/A-3 ter v običajnem neomejenem PDF isti klic uspe in vrne neničelni ID skupine. Rezultat nič torej ni splošna napaka, ki bi jo morali preučiti kasneje; knjižnica vam s tem sporoča, da aktivna raven skladnosti ne dopušča te funkcije

var
  Pdf: TPDFlib;
  LayerID: Integer;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.NewDocument;
    Pdf.SetPDFAMode(1);                       // PDF/A-1a: OCProperties forbidden

    LayerID := Pdf.NewOptionalContentGroup('Utilities');
    if LayerID = 0 then
      // zavrnjeno v PDF/A-1; ne gre za začasno napako, način prepoveduje plasti
      ShowMessage('Optional content is not available in PDF/A-1 mode.');
  finally
    Pdf.Free;
  end;
end;

Dve stanji na plast, ne eno

Plast ni preprosto le vidna ali nevidna. Privzeta konfiguracija beleži njeno stanje na zaslonu in ločeno stanje za tiskanje, saj §8.11.4 razlikuje med tem, kaj pregledovalnik prikazuje in kaj oddaja tiskalni cevovod. Obe stanji sta namenoma neodvisni. Vodni znak za osnutek je lahko prikazan na zaslonu in izpuščen na papirju, plast z linijami za izrez pa je lahko skrita na zaslonu, a kljub temu poslana na risalnik. Združitev obeh stanj bi prisilila eno, da sledi drugemu, s čimer bi izgubili natanko tisti nadzor, zaradi katerega funkcija sploh obstaja

PDF Library for Delphi izpostavlja ta par prek dveh nastavitvenih metod. SetOptionalContentGroupVisible sprejme ID skupine in zastavico (pri čemer ena pomeni vidno in nič skrito) ter upravlja privzeto stanje na zaslonu. SetOptionalContentGroupPrintable sprejme ID skupine in zastavico za to, ali se plast odda pri tiskanju dokumenta. Ustrezni bralni metodi, GetOptionalContentGroupVisible in GetOptionalContentGroupPrintable, vrneta ena ali nič, tako da lahko ločeno preberete zaslonsko in tiskalno stanje plasti, namesto da bi eno sklepali iz drugega

Matrika neodvisnih stanj vidnosti na zaslonu in v tisku za plasti PDF, ustvarjene z Delphi PDF Library: pomožne plasti ostanejo vidne povsod, označbe recenzenta se pokažejo na zaslonu, vendar se nikoli ne natisnejo, rezalna črta ploterja pa se na zaslonu skrije, a se vseeno natisne
Vidnost na zaslonu in oddajanje pri tiskanju sta nastavljena na skupino, tako da lahko pomožne plasti nastopajo povsod, revizorske oznake pa ostanejo opomba samo za zaslon

Izdelava dveh plasti na strani

Ustvarjanje plasti in njeno polnjenje poteka po določenem vrstnem redu. Na trenutno stran narišete vsebino za plast, nato pa pokličete SetContentStreamOptional z ID-jem skupine, kar ovije trenutni tok vsebine strani, tako da vse, kar je bilo izrisano do tistega trenutka, pripada tej skupini. Ker klic zajame vse, kar je v tistem trenutku v toku, je pravilo takšno, da najprej izrišete oznake ene plasti, jih dodelite in šele nato začnete z naslednjo plastjo. Spodnji primer postavi instalacije na prvo stran in ocenjevalčeve rdeče oznake na drugo stran, nastavi zaslonsko in tiskalno stanje vsake plasti ter shrani

var
  Pdf: TPDFlib;
  FontID, UtilLayer, RedlineLayer: Integer;
begin
  Pdf := TPDFlib.Create(nil);
  try
    Pdf.NewDocument;                          // unconstrained PDF: layers allowed
    Pdf.SetPageDimensions(595, 842);          // A4 in points
    FontID := Pdf.AddStandardFont(0);         // Helvetica
    Pdf.SelectFont(FontID);

    // Plast 1: pripomočki, narisani in nato dodeljeni lastni skupini
    Pdf.SetTextColor(0.10, 0.30, 0.65);
    Pdf.DrawText(72, 770, 'Utilities: water main, valve chamber');
    UtilLayer := Pdf.NewOptionalContentGroup('Utilities');
    Pdf.SetContentStreamOptional(UtilLayer);
    Pdf.SetOptionalContentGroupVisible(UtilLayer, 1);   // prikazano na zaslonu
    Pdf.SetOptionalContentGroupPrintable(UtilLayer, 1); // in na papirju

    // Plast 2: pripombe pregledovalca na novi strani
    Pdf.InsertPages(2, 1);                     // dodaj eno stran za stranjo 1
    Pdf.SetTextColor(0.80, 0.10, 0.10);
    Pdf.DrawText(72, 770, 'REVIEW: revise valve spec before issue');
    RedlineLayer := Pdf.NewOptionalContentGroup('Reviewer markup');
    Pdf.SetContentStreamOptional(RedlineLayer);
    Pdf.SetOptionalContentGroupVisible(RedlineLayer, 1);    // vidno med pregledom
    Pdf.SetOptionalContentGroupPrintable(RedlineLayer, 0);  // never printed

    Pdf.SaveToFile('SitePlan_Layers.pdf');
  finally
    Pdf.Free;
  end;
end;

Plast z rdečimi oznakami je primer, ki ga je vredno omeniti. Prikazana je na zaslonu, da ocenjevalec vidi opombo, njena zastavica za tiskanje pa je nič, tako da izpis iste datoteke ne vsebuje besedila ocene. Ta asimetrija je bistvo ohranjanja ločenih dveh stanj

Branje konfiguracije nazaj

Branje plasti je drugačen sprehod skozi isto strukturo. Po nalaganju datoteke GetOptionalContentConfigCount poroča, koliko konfiguracijskih slovarjev vsebuje dokument; prva privzeta konfiguracija ima ID konfiguracije 1. Znotraj konfiguracije GetOptionalContentConfigOrderCount vrne število vnosov v drevesu vrstnega reda, ki jih indeksirate od 1 naprej. Za vsak vnos GetOptionalContentConfigOrderItemLabel vrne njegovo prikazano besedilo, GetOptionalContentConfigOrderItemLevel pa vrne njegovo globino gnezdenja, tako da je mogoče natančno rekonstruirati strukturo plošče s podplastmi, zamaknjenimi pod naslovi

Vsak vnos ima tudi svojo vrsto. GetOptionalContentConfigOrderItemType razlikuje med dejansko izbirno skupino vsebin in navadno besedilno oznako, ki obstaja le kot naslov razdelka v drevesu. Ta razlika je pomembna, ker so poizvedbe o stanju posamezne skupine smiselne le za resnične skupine. Za vnos skupine GetOptionalContentConfigState poroča, ali jo konfiguracija začne kot vklopljeno, izklopljeno ali jo pusti nespremenjeno, GetOptionalContentConfigLocked pa poroča, ali je uporabniku preprečeno njeno preklapljanje. Spodnja zanka izriše drevo vrstnega reda s stanjem in zaklenjenim statusom vsake skupine ter zamikom glede na raven

var
  Pdf: TPDFlib;
  Cfg, Count, I, ItemType, GroupID, Indent: Integer;
  Line: string;
begin
  Pdf := TPDFlib.Create(nil);
  try
    if Pdf.LoadFromFile('SitePlan_Layers.pdf', '') = 0 then Exit;
    if Pdf.GetOptionalContentConfigCount = 0 then Exit;

    Cfg := 1;                                  // privzeta konfiguracija
    Count := Pdf.GetOptionalContentConfigOrderCount(Cfg);
    for I := 1 to Count do
    begin
      Indent := Pdf.GetOptionalContentConfigOrderItemLevel(Cfg, I);
      Line := StringOfChar(' ', Indent * 2)
              + Pdf.GetOptionalContentConfigOrderItemLabel(Cfg, I);

      ItemType := Pdf.GetOptionalContentConfigOrderItemType(Cfg, I);
      if ItemType = 1 then                     // 1 = neobvezna skupina vsebine
      begin
        GroupID := Pdf.GetOptionalContentConfigOrderItemID(Cfg, I);
        case Pdf.GetOptionalContentConfigState(Cfg, GroupID) of
          1: Line := Line + '  [on]';
          2: Line := Line + '  [off]';
          3: Line := Line + '  [unchanged]';
        end;
        if Pdf.GetOptionalContentConfigLocked(Cfg, GroupID) = 1 then
          Line := Line + ' (locked)';
      end;
      // ItemType = 2 je naslov besedilne oznake; nima stanja po skupinah

      Writeln(Line);
    end;
  finally
    Pdf.Free;
  end;
end;

Dve podrobnosti zagotavljajo pravilnost te zanke. Indeks vrstnega reda je eno-osnovan, od 1 do skupnega števila, kar ustreza notranjemu številčenju drevesa v knjižnici. Poleg tega se klici za posamezne skupine izvedejo le, ko je vrsta elementa skupina, saj je besedilna oznaka naslov z imenom in ravnijo, nima pa stanja vklopljenosti, izklopljenosti ali zaklenjenosti, po katerem bi lahko poizvedovali. Če to zaščito izpustite, boste od oznake zahtevali stanje, ki ga nima

Zanka branja nazaj za nastavitve neobvezne vsebine v PDF Library for Delphi: naštej nastavitve, prehoj vnose vrstnega reda z osnovo ena, se razvejaj po ItemType, tako da se o stanju vklopljeno, izklopljeno ali zaklenjeno poizvedujejo le prave skupine, besedilne oznake pa se preskočijo
Branje plasti nazaj obhodi vnose vrstnega reda eno-osnovno in stanje preveri le za prave skupine, ker naslovna oznaka nima stanja on, off ali locked za poročanje

Kje se to umešča

Plasti so mehanizem predstavitve, zato jih mora pogon upoštevati na vsaki poti, ki izrisuje stran, izrisovalna stran pa je obravnavana v naših navodilih za večpogonsko izrisovanje v Delphi. Prepletajo se tudi s strukturo dokumenta, saj je ime plasti besedilo, namenjeno avtorju, bralcu pa koristi strukturiran oris plasti, kar se povezuje z delom v našem članku o označenem PDF in dostopni strukturi. Oboje se povezuje z vmesniki API za izbirno vsebino, ki so opisani tukaj in so na voljo kot del knjižnice Delphi PDF Library skupaj s funkcijami za strani, besedilo, pisave in skladnost, ki so obravnavane drugje na tem blogu