Tehnični članak

Neobvezna vsebina PDF: preklapljanje slojev v Delphiju

PDFium Component upravlja sloje neobvezne vsebine PDF (OCG) v Delphiju skozi dve metodi TPdf: InspectOptionalContent našteje vsak sloj skupaj z vidnostjo, ki jo bo PDFium dejansko izrisal, SaveAsOptionalContentConfigured pa zapiše preverjeno kopijo, v kateri so sloji, ki jih izberete, preklopljeni v ali iz. Druga metoda hkrati nevtralizira pravila Usage in /AS, ki bi sicer tiho razveljavila Vašo spremembo. Obe delujeta na dokumentu, ki je že odprt v TPdf, zato ni drugega razčlenjevalnika, ki bi ga bilo treba usklajevati s tem, kar prikazuje pregledovalnik

Zahteva ponavadi pride iz delavnice za CAD ali GIS: nabor risb se pošlje s kotami, pripombami in glavo risbe na ločenih slojih, stranka pa želi kopijo s skritimi kotami, preden gre k dobavitelju. PDFium neobvezno vsebino izriše pravilno, a njegov javni ABI nima funkcije za naštevanje OCG, izbiro konfiguracije ali preklop stanja sloja. Torej se spustite na raven objektov, uredite /OCProperties, shranite, znova naložite — in sloj je še vedno tam. Razlog je logika vidnosti PDFium in vredno je razumevanja, preden se dotaknete katerega koli bajta

Zakaj urejanje /ON in /OFF ne spremeni tega, kar PDFium izriše?

Urejanje tabel /ON in /OFF konfiguracijskega slovarja ne zadošča, ker PDFium pusti, da izrecno stanje znotraj lastnega slovarja /Usage OCG premaga ti tabeli, pravilo samodejnega stanja /AS pa lahko nato preglasi oboje. ISO 32000-1 §8.11.4 opisuje konfiguracije in slovarje uporabe kot ločena mehanizma; izrisovalnik PDFium ju zloži v eno samo odločitev, InspectOptionalContent pa jo reproducira v tem vrstnem redu:

  • Začnite pri /BaseState konfiguracije, kjer /ON in /Unchanged oba štejeta kot vidna in le /OFF skriva
  • Uporabite tabelo /ON konfiguracije, nato njeno tabelo /OFF, tako da skupina, navedena v obeh, konča skrita
  • Uporabite izrecno stanje Usage skupine za zahtevano uporabo, na primer /Usage << /View << /ViewState /OFF >> >>, ki preglsi vse zgoraj
  • Skupino, katere /Intent ne vsebuje ne /View ne /All, obravnavajte kot vidno, saj ne sodeluje pri vidnosti z namenom pogleda
  • Nazadnje poženite tabelo /AS izbrane konfiguracije, katere vnosi za ujemajoči se dogodek nastavijo stanje skupin, ki jih navajajo
Petstopenjska odločitev o vidnosti, ki jo PDFium Component reproducira za vsako skupino neobvezne vsebine PDF v Delphiju: BaseState določa začetek, tabeli ON in OFF konfiguracije se uporabita po vrsti, izrecni vnos Usage ViewState ali PrintState preglsi oboje, nesodelovanje Intent se šteje kot vidno, tabela AS pa teče nazadnje
Urejanje tabel ON in OFF ne zadošča, ker PDFium BaseState, obe tabeli, stanje Usage skupine in nazadnje pravila samodejnega stanja AS zloži v eno samo sodbo, ki jo InspectOptionalContent reproducira korak za korakom

Tretji korak je tisti, ki ljudi zažge. Datoteka, shranjena z orodjem za postavitev, pogosto nosi /ViewState /ON na vsakem OCG, PDFium potem ignorira Vašo skrbno urejeno tabelo /OFF: shranjevanje uspe, datoteka se čisto ponovno odpre in sloj še vedno slika. Za Print in Export OcExplicitUsageState najprej prebere PrintState ali ExportState in pade nazaj na ViewState, kadar posebnega vnosa ni, zato osamljen ViewState /ON pripne sloj tudi za tiskanje. Označena vsebina, ki se sklicuje na OCMD (§8.11.2.2), se nato razreši proti tem rezultatom na skupino, prek politike /P ali, če je prisoten, izraza vidnosti /VE

Kako naštejete sloje, ki jih bo PDFium dejansko pokazal?

TPdf.InspectOptionalContent vrne TPdfOptionalContentInventory, katerega tabela Groups nosi številko objekta vsakega OCG, ime, namene, tri stanja Usage, jezik, obseg povečave, zastavico Locked, indeks radijske skupine in izračunani EffectiveVisible. Metoda najprej od PDFium zahteva shranjevanje trenutnega dokumenta v pomnilniku, razširi toke objektov in preišče rezultat, tako da se spremembe, narejene prej v seji, odražajo. Indeks konfiguracije 0 je vedno privzeti slovar /D in vnosi /Configs sledijo od indeksa 1; privzeti argument -1 izbere indeks 0. Dokument brez /OCProperties naredi, da metoda vrne False z razlogom v ErrorMessage, namesto da sproži izjemo

procedure TFormMain.ListLayers;
var
  Inv: TPdfOptionalContentInventory;
  G: TPdfOptionalContentGroup;
begin
  // Usage je privzeto ocuView; -1 izbere konfiguracijo 0, slovar /D
  if not Pdf.InspectOptionalContent(Inv) then
  begin
    Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
    Exit;
  end;
  Memo1.Lines.Add(Format('Configuration %d: %s',
    [Inv.SelectedConfigurationIndex,
     string(Inv.Configurations[Inv.SelectedConfigurationIndex].Name)]));
  for G in Inv.Groups do
    Memo1.Lines.Add(Format('obj %d  %s  visible=%s  locked=%s  radio=%d',
      [G.ObjectNumber, string(G.Name),
       BoolToStr(G.EffectiveVisible, True),
       BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;

Tabela Memberships poroča vsak OCMD z njegovo Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), surovim besedilom VisibilityExpression in njegovim lastnim EffectiveVisible. Nekaj mejnih pravil je namernih. /P ima privzeto /AnyOn in OCMD brez skupin se šteje kot viden. Sklic na številko objekta, ki ni znan OCG, se obravnava kot viden, namesto da bi spodletel celoten izraz. Ovrednotenje /VE se ustavi pri globini gnezdenja 32 in vse globljeje obravnava kot skrito, kar preprečuje, da bi sovražen ali samonanašajoč se izraz pretvoril pregled v prekoračitev sklada

Zapis novega stanja sloja s SaveAsOptionalContentConfigured

TPdf.SaveAsOptionalContentConfigured vzame tabelo zapisov TPdfOptionalContentStateChange (številka objekta skupine plus Visible) in zapiše dokument, v katerem izbrana konfiguracija proizvede točno to stanje. Izbrana konfiguracija dobi /BaseState /ON plus popolni tabeli /ON in /OFF, ki pokrijeta vsako skupino, vsak OCG, ki že ima slovar Usage, pa dobi izrecen ViewState (ali PrintState / ExportState, slediti Options.Usage), ki se ujema z njegovim novim stanjem. S TPdfOptionalContentConfigureOptions.Default je ključ /AS izbrane konfiguracije odstranjen, tako da dogodek odpiranja, tiskanja ali izvoza ne more slojev preklopiti nazaj

procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer);
var
  Changes: TPdfOptionalContentStateChanges;
  Options: TPdfOptionalContentConfigureOptions;
  Report: TPdfOptionalContentConfigureReport;
begin
  SetLength(Changes, 2);
  Changes[0].GroupObjectNumber := DimensionsObj;
  Changes[0].Visible := False;
  Changes[1].GroupObjectNumber := NotesObj;
  Changes[1].Visible := True;

  // Konfiguracija 0, ocuView, DisableAutomaticState in EnforceRadioGroups True
  Options := TPdfOptionalContentConfigureOptions.Default;

  if not Pdf.SaveAsOptionalContentConfigured('C:\Out\Drawing-NoDims.pdf',
    Changes, Options, Report) then
    raise Exception.Create('Layer update rejected: ' + Report.ErrorMessage);

  Log(Format('%d of %d groups changed, %d Usage states rewritten, /AS removed: %s',
    [Report.ChangedGroupCount, Report.GroupCount,
     Report.UpdatedUsageStateCount,
     BoolToStr(Report.RemovedAutomaticState, True)]));
end;

Zapisovalna pot obdrži PDFiumov lasten shranjeni izhod kot predpono, bajt za bajtom, in pripne le prenovljenega lastnika konfiguracije ter objekte OCG, ki nosijo slovarje Usage, ki jim sledi nov razdelek xref in trailer. Preden en sam bajt doseže Vaš cilj, je rezultat znova odprt v ločenem TPdf pod strogo politiko nalaganja, metoda pa spodleti, če tabela navzkrižnih sklicev ni veljavna. Preobremenitev za datoteko gre še korak dlje: piše v začasno datoteko poleg cilja in cilj zamenja šele, ko se potrditev uspe, zato zavrnjena posodobitev nikoli ne pusti za sabo polzapisane risbe. To je isti pristop preverjene inkrementalne revizije, ki ga uporablja urejevalnik imenskih in številčnih dreves PDF v PDFium Component

Kako SaveAsOptionalContentConfigured v komponenti PDFium Component zapiše Delphi PDF s preklopljenimi sloji: spremembe stanja in možnosti gredo noter, izbrana konfiguracija se prenove s popolnima tabelama ON in OFF ter stanji Usage, preverjena inkrementalna revizija se pripne in strog ponovni odprtje mora biti veljaven, preden se kaj zapiše
Konfigurirano shranjevanje obdrži PDFiumov lasten ponovni zapis kot bajtno predpono, pripne prenovljenega lastnika konfiguracije plus nov razdelek xref in rezultat znova odpre v ločenem TPdf, preden se cilj kadarkoli dotakne

Česa se konfigurirano shranjevanje odklanja?

Konfigurirano shranjevanje odklanja vsako spremembo, ki jo dokument sam prepoveduje ali je ne zna varno predstaviti, vsaka zavrnitev pa se zgodi, preden se cilj kadarkoli dotakne. Številka objekta, ki ni v /OCGs, spodleti brez pogajanj. Sprememba skupine, navedene v tabeli /Locked konfiguracije, spodleti, čeprav je ponovitev njene trenutne vrednosti dovoljena. Z vklopljenim EnforceRadioGroups je vsaka množica /RBGroups, ki bi končala z več kot enim vidnim članom, zavrnjena, namesto da bi druge tiho izklopila. Šifrirani dokumenti so zavrnjeni, ker inkrementalni objekti v čistem besedilu ne morejo nositi dejavnega varnostnega upravljalnika. Podpisani dokumenti sprožijo EPdfError, razen če podate AllowSignedDocument = True, saj lahko sprememba tega, kaj stran prikazuje, pokvari pokritost podpisa ali politiko overitve

Pregrade zavrnitev, ki jih SaveAsOptionalContentConfigured uveljavlja v komponenti PDFium Component pred zapisom konfiguriranega Delphi PDF: številka objekta izven OCGs spodleti, zaklenjene skupine spodletijo, množice RBGroups z več kot enim vidnim članom so zavrnjene, šifrirani dokumenti ne morejo nositi inkrementalnih objektov v čistem besedilu, podpisane datoteke pa zahtevajo AllowSignedDocument
Vsaka zavrnitev se zgodi, preden se cilj kadarkoli dotakne, razlog za spodletelost pa pristane v Report.ErrorMessage, namesto da bi za sabo pustila polzapisano risbo
function TFormMain.SavePrintPreset(Target: TStream;
  const Changes: TPdfOptionalContentStateChanges): Boolean;
var
  Options: TPdfOptionalContentConfigureOptions;
  Report: TPdfOptionalContentConfigureReport;
begin
  Options := TPdfOptionalContentConfigureOptions.Default;
  Options.Usage := ocuPrint;          // zapiše /Print << /PrintState ... >>
  Options.ConfigurationIndex := 1;    // prvi vnos /Configs, ne /D
  try
    Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
      Report);                        // AllowSignedDocument ostane False
    if not Result then
      ShowMessage(Report.ErrorMessage);
  except
    on E: EPdfError do
    begin
      ShowMessage(E.Message);         // podpisanca: v Target ni zapisano nič
      Result := False;
    end;
  end;
end;

Poznajte kompromise, preden to povežete v paketno opravilo. Pripeta revizija leži na vrhu polnega ponovnega zapisa PDFium, ne Vaših izvirnih bajtov datoteke — točno zato potrebuje podpisan vnos izrecno privolitev. Prenova tudi normalizira izbrano konfiguracijo na /BaseState /ON, tako da je izhodišče /Unchanged ali /OFF avtorja zamenjano z izrecnima tabelama z isto posledično vidnostjo. Odstranitev /AS odstrani trike le za tiskanje, kot je sloj z vodnim žigom, ki se pokaže le na papirju; nastavite DisableAutomaticState na False, da ta pravila ohranite, in sprejmite, da lahko preglasijo Vaše zahtevano stanje za ta dogodek. Na pozitivni strani PDF/A-2 (ISO 19005-2 točka 6.9) in PDF/UA (ISO 14289-1 točka 7.10) oba prepovedujeta /AS v konfiguracijskih slovarjih, zato privzeti izhod odstrani eno težavo, ki bi jo Vaša validacija PDF/A preflight s komponento PDFium Component sicer prijavila

Kje se nadzor slojev ujema s pregledovalnikom PDF v Delphiju

V pregledovalniku je nadzor slojev kontrolni seznam, ki ga poganja inventar plus ponovno nalaganje shranjenega rezultata. Kontrolni seznam napolnite iz Groups, onemogočite vnose, ki so Locked, člane z istim RadioGroupIndex obravnavajte kot medsebojno izključujoče in ob uporabi zapišite v TMemoryStream ter ta tok naložite nazaj v TPdf, tako da pogled slika novo stanje. Ožičenje med TPdf in TPdfView pokriva gradnja bogatega pregledovalnika PDF s PDFium VCL v Delphiju. Licenciranje, preskusni prenosi in preostanek nabora funkcij so na strani izdelka PDFium Component for Delphi