Un topograf deschide un plan de situație și dorește ca liniile de contur să fie ascunse în timp ce rețelele de utilități rămân vizibile. Un evaluator dorește ca adnotările de revizuire să fie vizibile pe ecran și eliminate din documentul tipărit. O fișă de produs este livrată în trei limbi dintr-un singur fișier, iar cititorul alege ce limbă este afișată. Toate cele trei reprezintă aceeași funcție PDF, iar panoul care le controlează în Acrobat se numește Layers. Funcția din spatele acestui panou este conținutul opțional (optional content) și este ceea ce permite unei singure pagini să conțină mai multe straturi vizuale independente pe care un vizualizator le activează sau dezactivează.
Conținutul opțional este specificat în ISO 32000-1 §8.11. Unitatea de vizibilitate este un grup de conținut opțional (optional content group), un OCG, un dicționar de tip /OCG care poartă un nume. Conținutul marcat de pe o pagină este asociat cu un grup, iar vizualizatorul decide dacă acel grup este afișat în prezent. O construcție înrudită, dicționarul de apartenență la conținutul opțional (optional content membership dictionary) sau OCMD, permite ca vizibilitatea să depindă de o combinație booleană a mai multor grupuri, dar cazul uzual este un singur grup numit care reprezintă un singur strat. Documentul leagă întregul mecanism prin intermediul unei singure intrări din catalog, /OCProperties, descrisă în continuare.
Ce trebuie să conțină catalogul
Un OCG în sine este inert. Pentru ca un vizualizator să listeze un strat și să îi rețină starea, catalogul documentului are nevoie de un dicționar /OCProperties, iar §8.11.4 stabilește exact ce conține acesta. Există un tablou /OCGs care numește fiecare grup din fișier și există o intrare /D care conține configurația implicită. Configurația implicită este partea pe care un cititor o aplică atunci când fișierul se deschide prima dată. Aceasta înregistrează care grupuri pornesc activate și care dezactivate, care intrări sunt blocate împotriva comutării de către utilizator și, prin intermediul unui tablou /Order, cum sunt aranjate și imbricate numele straturilor în panou.
Consecința practică este că crearea unui strat nu este niciodată un acțiune pur locală. Grupul trebuie desenat pe pagină și, de asemenea, trebuie înregistrat într-o structură la nivel de catalog care nu exista anterior. PDFlibPas le face pe ambele pentru dumneavoastră. Primul apel care creează un grup adaugă intrarea /OCProperties în catalog și inițializează configurația implicită, astfel încât stratul este atât desenat, cât și listat fără o evidență separată din partea dumneavoastră.
De ce un mod de conformitate poate bloca această funcție
Înainte ca orice cod de straturi să ruleze, ținta de conformitate a documentului decide dacă conținutul opțional este legal. PDF/A-1, profilul de arhivare definit în ISO 19005-1, interzice direct intrarea /OCProperties în §6.1.13. Raționamentul se potrivește scopului formatului. Un fișier de arhivă trebuie să se redea identic pentru fiecare cititor în viitorul îndepărtat, iar conținutul a cărui vizibilitate poate fi modificată de un vizualizator este un conținut al cărui aspect nu este fix, așa că profilul interzice această construcție în loc să permită o arhivă ambiguă. PDF/A-2 și PDF/A-3, definite în ISO 19005-2 și ISO 19005-3, adoptă punctul de vedere opus în §6.9 și permit conținutul opțional, cu reguli despre vizibilitatea implicită.
Această diferență se vede direct în API. Când documentul se află în modul PDF/A-1, NewOptionalContentGroup refuză să creeze grupul și returnează zero, deoarece onorarea cererii ar produce un fișier care eșuează la propria conformitate declarată. În modul PDF/A-2 sau PDF/A-3, și în PDF-ul obișnuit fără constrângeri, același apel reușește și returnează un ID de grup diferit de zero. Prin urmare, un rezultat zero nu este o eroare generică pe care să o inspectați mai târziu; este biblioteca care vă spune că nivelul de conformitate activ nu permite această caracteristică.
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;
Două stări per strat, nu una
Un strat nu este pur și simplu vizibil sau invizibil. Configurația implicită înregistrează starea sa pe ecran și o stare separată de tipărire, deoarece §8.11.4 distinge ceea ce afișează un vizualizator de ceea ce emite un flux de tipărire. Cele două sunt independente în mod intenționat. Un filigran ciornă (draft) poate fi afișat pe ecran și omis pe hârtie, iar un strat cu linii de tăiere poate fi ascuns pe ecran, dar trimis la un plotter. Unificarea celor două ar forța-o pe una să o urmeze pe cealaltă și ar duce la pierderea controlului exact pe care această caracteristică există să îl ofere.
PDFlibPas expune această pereche prin intermediul a două metode de setare. SetOptionalContentGroupVisible preia ID-ul grupului și un flag, unde unu înseamnă vizibil și zero înseamnă ascuns, și guvernează starea implicită pe ecran. SetOptionalContentGroupPrintable preia ID-ul grupului și un flag pentru a indica dacă stratul este emis atunci când documentul se tipărește. Metodele corespunzătoare de citire, GetOptionalContentGroupVisible and GetOptionalContentGroupPrintable, returnează fiecare unu sau zero, astfel încât să puteți citi separat dispunerea ecranului și cea de tipărire ale unui strat, în loc să o deduceți pe una din cealaltă.
Construirea a două straturi pe o pagină
Crearea unui strat și umplerea acestuia urmează o ordine fixă. Desenați conținutul pentru strat pe pagina curentă, apoi apelați SetContentStreamOptional cu ID-ul grupului, ceea ce împachetează fluxul de conținut curent al paginii, astfel încât tot ceea ce s-a desenat până acum să aparțină acelui grup. Deoarece apelul captează tot ceea ce se află în flux în acel moment, regula este să plasați marcajele unui strat, să le atribuiți și abia apoi să începeți următorul strat. Exemplul de mai jos plasează utilitățile pe prima pagină și o linie de revizuire roșie pe a doua pagină, setează starea de ecran și de tipărire pentru fiecare strat și salvează.
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;
Stratul de revizuire (redline) este cazul care merită remarcat. Este afișat pe ecran pentru ca un evaluator să vadă nota, iar flag-ul său de tipărire este zero, astfel încât o imprimare a aceluiași fișier să nu conțină textul de revizuire. Această asimetrie este tocmai motivul pentru care cele două stări sunt menținute separate.
Citirea înapoi a configurației
Citirea straturilor este o parcurgere diferită a aceleiași structuri. După încărcarea unui fișier, GetOptionalContentConfigCount raportează câte dicționare de configurare conține documentul; prima configurație implicită are ID-ul de configurare 1. În cadrul unei configurări, GetOptionalContentConfigOrderCount oferă numărul de intrări din arborele de ordine, iar dumneavoastră le indexați de la 1. Pentru fiecare intrare, GetOptionalContentConfigOrderItemLabel returnează textul său de afișare și GetOptionalContentConfigOrderItemLevel returnează adâncimea sa de imbricare, astfel încât o structură de panou cu sub-straturi indentate sub titluri să poată fi reconstruită textual.
Fiecare intrare are, de asemenea, un tip. GetOptionalContentConfigOrderItemType distinge un grup de conținut opțional real de o etichetă de text simplu care există doar pentru a conduce o secțiune a arborelui. Această distincție contează deoarece interogările de stare per grup au sens doar pentru grupurile reale. Pentru o intrare de grup, GetOptionalContentConfigState raportează dacă configurația îl pornește activat, dezactivat sau îl lasă neschimbat, iar GetOptionalContentConfigLocked raportează dacă utilizatorul are interdicție de a-l comuta. Bucla de mai jos redă arborele de ordine cu starea fiecărui grup și starea de blocare, indentând în funcție de nivel.
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;
Două detalii mențin această buclă corectă. Indexul de ordine începe de la unu, de la 1 la număr, corespunzând modului în care biblioteca numerotează arborele la nivel intern. Și apelurile per grup rulează numai când tipul elementului este un grup, deoarece o etichetă de text este un titlu cu un nume și un nivel, dar fără stare activată, dezactivată sau blocată de interogat. Omiteți această protecție și veți cere unei etichete o stare pe care nu o are.
Unde se încadrează toate acestea
Straturile sunt un mecanism de prezentare, așa că motorul trebuie să le respecte pe fiecare cale care redă o pagină, iar partea de redare este acoperită în ghidul nostru de redare cu motoare multiple în Delphi. De asemenea, ele se intersectează cu structura documentului, deoarece numele unui strat este un text vizibil pentru autor, iar un cititor beneficiază de o structură ierarhică de straturi, ceea ce se conectează cu lucrul din articolul nostru despre PDF marcat și structura de accesibilitate. Ambele se asociază cu API-urile de conținut opțional descrise aici, care sunt livrate ca parte a Delphi PDF Library alături de facilitățile de pagină, text, fonturi și conformitate discutate în alte părți pe acest blog.