Matininkas atidaro sklypo planą ir nori, kad kontūrai būtų paslėpti, o komunalinės paslaugos liktų matomos. Recenzentas nori, kad raudonos linijos anotacijos būtų matomos ekrane, bet dingtų iš spaudinio. Produkto lapas išsiunčiamas trimis kalbomis iš vieno failo, o skaitytojas pasirenka, kuri kalba rodoma. Visi trys yra ta pati PDF funkcija, o skydelis, kuris juos valdo Acrobat programoje, vadinamas Layers (Sluoksniai). Po šiuo skydeliu esanti funkcija yra pasirenkamas turinys (angl. optional content), ir būtent ji leidžia viename puslapyje turėti kelis nepriklausomus vaizdo sluoksnius, kuriuos peržiūros programa įjungia ir išjungia
Pasirenkamas turinys yra apibrėžtas ISO 32000-1 §8.11 skyriuje. Matomumo vienetas yra pasirenkamo turinio grupė, OCG, /OCG tipo žodynas, turintis pavadinimą. Pažymėtas turinys puslapyje yra susietas su grupe, o peržiūros programa nusprendžia, ar ta grupė šiuo metu rodoma. Susijusi konstrukcija, pasirenkamo turinio narystės žodynas arba OCMD, leidžia matomumui priklausyti nuo kelių grupių loginio derinio, tačiau kasdienis atvejis yra viena įvardyta grupė, atitinkanti vieną sluoksnį. Dokumentas susieja visą mechanizmą per vieną katalogo įrašą, /OCProperties, aprašytą toliau
Ką katalogas turi turėti
OCG pati savaime yra inertiška. Kad peržiūros programa galėtų pateikti sluoksnį ir prisiminti jo būseną, dokumentų katalogui reikia /OCProperties žodyno, o §8.11.4 skyriuje tiksliai išdėstyta, kas į jį įeina. Greta yra /OCGs masyvas, nurodantis kiekvieną failo grupę, ir yra /D įrašas, kuriame saugoma numatytoji konfigūracija. Numatytoji konfigūracija yra ta dalis, kurią skaitytuvas taiko pirmą kartą atidarius failą. Ji fiksuoja, kurios grupės pradedamos rodyti, o kurios ne, kurie įrašai yra užrakinti nuo vartotojo perjungimo, ir per /Order masyvą nurodo, kaip sluoksnių pavadinimai yra išdėstyti ir įterpti skydelyje
Praktinė pasekmė yra ta, kad sluoksnio kūrimas niekada nėra grynai vietinis veiksmas. Sluoksnio grupė turi būti nupiešta puslapyje, taip pat ji turi būti užregistruota katalogo lygio struktūroje, kurios anksčiau nebuvo. PDFlibPas atlieka abu veiksmus už jus. Pirmasis iškvietimas, sukuriantis grupę, prideda /OCProperties įrašą į katalogą ir užpildo numatytąją konfigūraciją, todėl sluoksnis yra ir nupiešiamas, ir įtraukiamas į sąrašą be atskiros apskaitos jūsų pusėje
Kodėl atitikties režimas gali neleisti naudoti šios funkcijos
Prieš paleidžiant bet kokį sluoksnio kodą, dokumento atitikties tikslas nusprendžia, ar pasirenkamas turinys išvis yra teisėtas. PDF/A-1, archyvavimo profilis, apibrėžtas ISO 19005-1, visiškai draudžia /OCProperties įrašą §6.1.13 skyriuje. Šis argumentas atitinka formato paskirtį. Archyvinis failas turi būti atvaizduojamas identiškai kiekvienam skaitytojui tolimoje ateityje, o turinys, kurio matomumą peržiūros programa gali pakeisti, yra turinys, kurio išvaizda nėra fiksuota, todėl profilis draudžia šią konstrukciją, užuot leidęs dviprasmišką archyvą. PDF/A-2 ir PDF/A-3, apibrėžti ISO 19005-2 ir ISO 19005-3, laikosi priešingos nuomonės savo §6.9 skyriuje ir leidžia pasirenkamą turinį su taisyklėmis dėl numatytojo matomumo
Šis skirtumas tiesiogiai atsispindi API. Kai dokumentas yra PDF/A-1 režime, NewOptionalContentGroup atsisako sukurti grupę ir grąžina nulį, nes patenkinus užklausą būtų sukurtas failas, kuris nepraeina savo paties deklaruotos atitikties. PDF/A-2 arba PDF/A-3 režimuose ir įprastame neribotame PDF tas pats iškvietimas pavyksta ir grąžina ne nulinį grupės ID. Todėl nulinis rezultatas nėra bendra klaida, kurią reikėtų tikrinti vėliau; tai yra biblioteka, pranešanti, kad aktyvus atitikties lygis neturi vietos šiai funkcijai
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
// refused under PDF/A-1; not a transient error, the mode bans layers
ShowMessage('Optional content is not available in PDF/A-1 mode.');
finally
Pdf.Free;
end;
end;
Dvi būsenos vienam sluoksniui, o ne viena
Sluoksnis nėra tiesiog matomas arba nematomas. Numatytojoje konfigūracijoje fiksuojama jo būsena ekrane ir atskira spausdinimo būsena, nes §8.11.4 skyrius skiria tai, ką rodo peržiūros programa, nuo to, ką išveda spausdinimo konvejeris. Šie du dalykai yra nepriklausomi tyčia. Juodraščio vandens ženklas gali būti rodomas ekrane ir pašalintas iš popieriaus, o pjovimo linijos sluoksnis gali būti paslėptas ekrane, bet išsiųstas į braižytuvą. Sujungus šiuos du dalykus, vienas turėtų sekti kitą ir būtų prarasta būtent ta kontrolė, kuriai suteikti ši funkcija egzistuoja
PDFlibPas atskleidžia šią porą per du nustatymo metodus (angl. setters). SetOptionalContentGroupVisible priima grupės ID ir vėliavėlę, kur vienetas reiškia matomą, o nulis – paslėptą, ir valdo numatytąją būseną ekrane. SetOptionalContentGroupPrintable priima grupės ID ir vėliavėlę, nurodančią, ar sluoksnis yra išvedamas spausdinant dokumentą. Atitinkamos gavimo funkcijos (angl. getters) GetOptionalContentGroupVisible ir GetOptionalContentGroupPrintable grąžina vienetą arba nulį, todėl galite atskirai perskaityti sluoksnio ekrano ir spausdinimo nustatymus, užuot darę prielaidą apie vieną iš kito
Dviejų sluoksnių kūrimas puslapyje
Sluoksnio kūrimas ir jo užpildymas vyksta nustatyta tvarka. Jūs nupiešiate sluoksnio turinį dabartiniame puslapyje, tada iškviečiate SetContentStreamOptional su grupės ID, kuris apgaubia dabartinį puslapio turinio srautą, kad viskas, kas nupiešta iki šiol, priklausytų tai grupei. Kadangi iškvietimas užfiksuoja viską, kas tuo metu yra sraute, taisyklė yra nupiešti vieno sluoksnio žymes, jas priskirti ir tik tada pradėti kitą sluoksnį. Žemiau pateiktame pavyzdyje komunalinės paslaugos perkeliamos į pirmąjį puslapį, o recenzento raudonos linijos – į antrąjį puslapį, nustatoma kiekvieno sluoksnio ekrano ir spaudinio būsena bei išsaugoma
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);
// Layer 1: utilities, drawn then assigned to its own group
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); // shown on screen
Pdf.SetOptionalContentGroupPrintable(UtilLayer, 1); // and on paper
// Layer 2: reviewer redline on a fresh page
Pdf.InsertPages(2, 1); // append one page after page 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); // visible while reviewing
Pdf.SetOptionalContentGroupPrintable(RedlineLayer, 0); // never printed
Pdf.SaveToFile('SitePlan_Layers.pdf');
finally
Pdf.Free;
end;
end;
Raudonos linijos sluoksnis yra tas atvejis, kurį verta pažymėti. Jis rodomas ekrane, kad recenzentas matytų pastabą, o jo spausdinimo vėliavėlė yra nulis, todėl to paties failo spaudinyje nėra jokio peržiūros teksto. Ši asimetrija yra visa esmė, kodėl šios dvi būsenos laikomos atskirai
Konfigūracijos skaitymas atgal
Sluoksnių skaitymas yra kitoks perėjimas per tą pačią struktūrą. Įkėlus failą, GetOptionalContentConfigCount praneša, kiek konfigūracijos žodynų yra dokumente; pirmoji numatytoji konfigūracija yra konfigūracijos ID 1. Konfigūracijoje GetOptionalContentConfigOrderCount nurodo įrašų skaičių užsakymų medyje, o jūs juos indeksuojate nuo 1. Kiekvienam įrašui GetOptionalContentConfigOrderItemLabel grąžina jo rodomą tekstą, o GetOptionalContentConfigOrderItemLevel – jo įdėjimo gylį, todėl skydelio struktūra su subsluoksniais, įtrauktais po antraštėmis, gali būti rekonstruota pažodžiui
Kiekvienas įrašas taip pat turi tipą. GetOptionalContentConfigOrderItemType skiria tikrąją pasirenkamo turinio grupę nuo paprasto teksto etiketės, kuri egzistuoja tik tam, kad pradėtų medžio sekciją. Šis skirtumas yra svarbus, nes užklausos apie grupės būseną turi prasmę tik tikroms grupėms. Grupės įrašui GetOptionalContentConfigState praneša, ar konfigūracija ją paleidžia įjungtą, išjungtą, ar palieka nepakeistą, o GetOptionalContentConfigLocked praneša, ar vartotojui draudžiama ją perjungti. Žemiau esantis ciklas atvaizduoja užsakymų medį su kiekvienos grupės būsena ir užrakinimo būsena, pritaikant įtrauką pagal lygį
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; // the default configuration
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 = optional content group
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 is a text label heading; it has no per-group state
Writeln(Line);
end;
finally
Pdf.Free;
end;
end;
Dvi detalės užtikrina šio ciklo teisingumą. Užsakymo indeksas prasideda nuo vieneto, nuo 1 iki skaičiaus, atitinkant tai, kaip biblioteka numeruoja medį viduje. O iškvietimai atskiroms grupėms veikia tik tada, kai elemento tipas yra grupė, nes teksto etiketė yra antraštė su pavadinimu ir lygiu, bet neturinti įjungtos, išjungtos ar užrakintos būsenos, kurią būtų galima užklausti. Praleiskite šią apsaugą ir užklausite etiketės būsenos, kurios ji neturi
Kur tai tinka
Sluoksniai yra pateikimo mechanizmas, todėl variklis turi juos gerbti kiekviename kelyje, kuriuo atvaizduojamas puslapis, o atvaizdavimo pusė yra aprašyta mūsų kelių variklių atvaizdavimo apžvalgoje Delphi aplinkoje. Jie taip pat kertasi su dokumento struktūra, nes sluoksnio pavadinimas yra autoriaus tekstas, o skaitytojui naudinga struktūrizuota sluoksnių schema, kuri siejasi su darbu, aprašytu mūsų straipsnyje apie pažymėtą PDF ir prieinamumo struktūrą. Abu dera su čia aprašytomis pasirenkamo turinio API, kurios platinamos kaip Delphi PDF biblioteka šalia puslapių, teksto, šriftų ir atitikties priemonių, aptariamų kitur šiame tinklaraštyje