Tehnički članak

Upravljanje PDF optional content slojevima u Delphiju

PDFium Component upravlja PDF optional content slojevima (OCG) u Delphiju kroz dvije metode TPdf: InspectOptionalContent izlistava svaki sloj zajedno s vidljivošću koju PDFium stvarno renderira, a SaveAsOptionalContentConfigured piše verificiranu kopiju u kojoj su slojevi koje Vi izaberete uključeni ili isključeni. Druga metoda također neutralizira Usage i /AS pravila koja bi inače tiho poništila Vašu izmjenu. Obje rade nad dokumentom već otvorenim u TPdf, pa nema drugog parsera koji bi se držao u sunku s onim što viewer prikazuje

Zahtjev obično dolazi iz CAD ili GIS radionice: set crteža isporučuje se s dimenzijama, anotacijama i naslovnim blokom na odvojenim slojevima, a kupac želi kopiju sa skrivenim dimenzijama prije nego otide dobavljaču. PDFium ispravno renderira optional content, ali njegov javni ABI nema funkciju za enumeraciju OCG-ova, odabir konfiguracije ili preokret stanja sloja. Pa siđete na razinu objekata, uredite /OCProperties, spremite, učitate iznova, i sloj je još uvijek tu. Razlog je PDFium logika vidljivosti, i vrijedi je razumjeti prije nego dirate ijedan bajt

Zašto uređivanje /ON i /OFF ne mijenja ono što PDFium renderira?

Uređivanje polja /ON i /OFF rječnika konfiguracije nije dovoljno, jer PDFium pušta da eksplicitno stanje unutar vlastitog /Usage rječnika OCG-a pobijedi ta polja, a /AS auto-state pravilo može zatim nadjačati oboje. ISO 32000-1 §8.11.4 opisuje konfiguracije i usage rječnike kao odvojene mehanizme; PDFium ih renderer savija u jednu odluku, a InspectOptionalContent je reproducira ovim redoslijedom:

  • Počnite od /BaseState konfiguracije, gdje /ON i /Unchanged oba računaju se vidljivima i samo /OFF skriva
  • Primijenite polje /ON konfiguracije, zatim njeno polje /OFF, pa grupa navedena u oba završi skrivena
  • Primijenite eksplicitno Usage stanje grupe za traženu uporabu, poput /Usage << /View << /ViewState /OFF >> >>, koje nadjačava sve gore navedeno
  • Grupu čiji /Intent ne sadrži ni /View ni /All tretirajte kao vidljivu, jer ne sudjeluje u vidljivosti view-intenta
  • Na kraju pokrenite polje /AS odabrane konfiguracije, čiji unosi za odgovarajući događaj postavljaju stanje grupa koje izlistavaju
Petostupanjska odluka o vidljivosti koju PDFium Component reproducira za svaku PDF optional content grupu u Delphiju: BaseState postavlja početak, polja ON i OFF konfiguracije primjenjuju se redom, eksplicitni Usage ViewState ili PrintState unos nadjačava oboje, nesudjelovanje Intenta računa se vidljivim, a polje AS izvodi se zadnje
Uređivanje polja ON i OFF nije dovoljno jer PDFium BaseState, oba polja, Usage stanje grupe i napokon AS auto-state pravila savija u jednu presudu koju InspectOptionalContent reproducira korak po korak

Treći je korak onaj koji ljude opeče. Datoteka spremljena layout alatom često nosi /ViewState /ON na svakom OCG-u, pa PDFium tada ignorira Vaše pažljivo uređeno polje /OFF: spremanje uspije, datoteka se čisto otvara, i sloj se i dalje crta. Za Print i Export OcExplicitUsageState prvo čita PrintState ili ExportState i pada na ViewState kad specifični unos ne postoji, pa usamljeni ViewState /ON pribija sloj i za ispis. Markirani sadržaj koji referencira OCMD (§8.11.2.2) zatim se razrješuje protiv ovih rezultata po grupi, kroz politiku /P ili, kad postoji, vidljivosni izraz /VE

Kako izlistati slojeve koje će PDFium stvarno pokazati?

TPdf.InspectOptionalContent vraća TPdfOptionalContentInventory čije polje Groups nosi broj objekta svakog OCG-a, ime, intentove, tri Usage stanja, jezik, zoom raspon, zastavicu Locked, indeks radio grupe i izračunati EffectiveVisible. Metoda najprije naredi PDFiumu da spremi trenutni dokument u memoriji, širi object streamove i skenira rezultat, pa se izmjene učinjene ranije u sesiji odraze. Indeks konfiguracije 0 uvijek je zadani rječnik /D, a unosi /Configs slijede od indeksa 1; zadani argument -1 odabire indeks 0. Dokument bez /OCProperties natjera metodu da vrati False s razlogom u ErrorMessage umjesto da digne iznimku

procedure TFormMain.ListLayers;
var
  Inv: TPdfOptionalContentInventory;
  G: TPdfOptionalContentGroup;
begin
  // Usage je zadano ocuView; -1 odabire konfiguraciju 0, rječnik /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;

Polje Memberships javlja svaki OCMD s njegovim Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), sirovim tekstom VisibilityExpression i vlastitim EffectiveVisible. Par rubnih pravila namjerno je takvo. /P je zadano /AnyOn, a OCMD bez grupa računa se vidljivim. Referenca na broj objekta koji nije poznati OCG tretira se kao vidljiva umjesto da obori cijeli izraz. Evaluacija /VE staje na dubini ugniježđenja 32 i sve dublje tretira kao skriveno, što neprijateljskom ili samose referencirajućem izrazu sprječava da inspekciju pretvori u stack overflow

Pisanje novog stanja sloja sa SaveAsOptionalContentConfigured

TPdf.SaveAsOptionalContentConfigured prima polje zapisa TPdfOptionalContentStateChange (broj objekta grupe plus Visible) i piše dokument u kojem odabrana konfiguracija proizvodi točno to stanje. Odabrana konfiguracija dobiva /BaseState /ON plus potpuna polja /ON i /OFF koja pokrivaju svaku grupu, a svaki OCG koji već ima Usage rječnik prima eksplicitni ViewState (ili PrintState / ExportState, prema Options.Usage) koji odgovara njegovom novom stanju. S TPdfOptionalContentConfigureOptions.Default ključ /AS odabrane konfiguracije uklanja se da open, print ili export događaj ne može slojeve vratiti natrag

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

Putanja pisanja zadržava PDFium vlastiti spremljeni izlaz kao prefiks identičan do bajta i dodaje samo prepravljenog vlasnika konfiguracije i OCG objekte koji nose Usage rječnike, iza kojih slijede novi xref odsječak i trailer. Prije nego ijedan bajt dođe do Vašeg odredišta, rezultat se ponovno otvara u zasebnom TPdf pod strogom politikom učitavanja, i metoda pada ako tablica unakrsnih referenci ne prolazi validaciju. Overload s datotekom ide korak dalje: piše u privremenu datoteku pokraj ciljne i ciljnu mijenja tek nakon što verifikacija uspije, pa odbijena izmjena nikad ne ostavlja napola napisani crtež za sobom. To je isti pristup verificirane inkrementalne revizije koji koristi uređivač PDF name i number stabala u PDFium Componentu

Kako SaveAsOptionalContentConfigured u PDFium Componentu piše Delphi PDF s prebačenim slojevima: promjene stanja i opcije ulaze unutra, odabrana konfiguracija prepravlja se s potpunim poljima ON i OFF i Usage stanjima, verificirana inkrementalna revizija dodaje se, i strogo ponovno otvaranje mora proći validaciju prije nego se išta zapiše
Konfigurirano spremanje zadržava PDFium vlastiti re-save kao bajt prefiks, dodaje prepravljenog vlasnika konfiguracije plus novi xref odsječak, i rezultat ponovno otvara u zasebnom TPdf prije nego se odredište dotakne

Što konfigurirano spremanje odbija učiniti?

Konfigurirano spremanje odbija svaku izmjenu koju sam dokument zabranjuje ili ne može sigurno predstaviti, i svako odbijanje događa se prije nego se odredište dotakne. Broj objekta koji nije u /OCGs pada odmah. Mijenjanje grupe navedene u polju /Locked konfiguracije pada, iako je ponovno navođenje njene trenutne vrijednosti dopušteno. Uključenim EnforceRadioGroups, svaki skup /RBGroups koji bi završio s više od jednog vidljivog člana odbija se umjesto da se ostali tiho isključe. Šifrirani dokumenti se odbijaju jer otvoreni inkrementalni objekti ne mogu nositi aktivni sigurnosni handler. Potpisani dokumenti dižu EPdfError osim ako ne proslijedite AllowSignedDocument = True, jer promjena onoga što stranica prikazuje može slomiti pokrivenost potpisa ili politiku certifikacije

Kapije odbijanja koje SaveAsOptionalContentConfigured primjenjuje u PDFium Componentu prije pisanja konfiguriranog Delphi PDF-a: broj objekta izvan OCG-ova pada, zaključane grupe padaju, RBGroups skupovi s više od jednog vidljivog člana odbijaju se, šifrirani dokumenti ne mogu nositi otvorene inkrementalne objekte, a potpisane datoteke traže AllowSignedDocument
Svako odbijanje događa se prije nego se odredište dotakne, a razlog pada završava u Report.ErrorMessage umjesto da ostavi napola napisani crtež za sobom
function TFormMain.SavePrintPreset(Target: TStream;
  const Changes: TPdfOptionalContentStateChanges): Boolean;
var
  Options: TPdfOptionalContentConfigureOptions;
  Report: TPdfOptionalContentConfigureReport;
begin
  Options := TPdfOptionalContentConfigureOptions.Default;
  Options.Usage := ocuPrint;          // piše /Print << /PrintState ... >>
  Options.ConfigurationIndex := 1;    // prvi unos /Configs, ne /D
  try
    Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
      Report);                        // AllowSignedDocument ostaje False
    if not Result then
      ShowMessage(Report.ErrorMessage);
  except
    on E: EPdfError do
    begin
      ShowMessage(E.Message);         // potpisana datoteka: ništa nije zapisano u Target
      Result := False;
    end;
  end;
end;

Upoznajte kompromise prije nego ovo uključite u batch posao. Dodana revizija sjedi na vrhu PDFium punog re-savea, ne Vaših izvornih bajtova datoteke, i baš zato potpisani ulaz treba izričit pristanak. Prepravka također normalizira odabranu konfiguraciju na /BaseState /ON, pa se autorova /Unchanged ili /OFF baza zamjenjuje eksplicitnim poljima s istom rezultantnom vidljivošću. Ispuštanje /AS uklanja print-only trikove poput vodenog žiga koji se pojavljuje samo na papiru; postavite DisableAutomaticState na False da ta pravila zadržite, prihvaćajući da mogu nadjačati traženo stanje za taj događaj. Na plus strani, PDF/A-2 (ISO 19005-2 clause 6.9) i PDF/UA (ISO 14289-1 clause 7.10) oboje zabranjuju /AS u rječnicima konfiguracija, pa zadani izlaz uklanja jedan problem koji bi inače PDF/A preflight validacija s PDFium Componentom javila

Gdje se upravljanje slojevima uklapa u Delphi PDF viewer

U vieweru je upravljanje slojevima kontrolna lista koju pokreće inventar plus ponovno učitavanje spremljenog rezultata. Napunite kontrolnu listu iz Groups, onemogućite unose koji su Locked, članove koji dijele RadioGroupIndex tretirajte kao međusobno isključive, i pri primjeni pišite u TMemoryStream i učitajte taj stream natrag u TPdf da prikaz nacrta novo stanje. Povezivanje između TPdf i TPdfView pokriveno je u gradnji PDF viewera bogatog značajkama s PDFium VCL-om u Delphiju. Licenciranje, probna preuzimanja i ostatak skupa značajki su na stranici proizvoda PDFium Component za Delphi