Műszaki cikk

PDF opcionális rétegek kapcsolgatása Delphiben a PDFiummal

A PDFium Component a PDF opcionális tartalomrétegeit (OCG-ket) Delphiben két TPdf metóduson keresztül kezeli: az InspectOptionalContent minden réteget felsorol a PDFium által ténylegesen kirajzolt láthatósággal együtt, a SaveAsOptionalContentConfigured pedig egy ellenőrzött másolatot ír, amelyben az Ön által választott rétegek be vagy ki vannak kapcsolva. A második metódus emellett hatástalanítja azokat a Usage és /AS szabályokat, amelyek egyébként csendben visszacsinálnák a módosítását. Mindkettő a TPdf-ben már megnyitott dokumentumon dolgozik, tehát nincs második elemző, amelyet a megjelenítővel szinkronban kellene tartani

A kérés jellemzően egy CAD vagy GIS műhelyből érkezik: a rajzkészlet méretezést, annotációkat és címsort külön rétegeken szállít, és a vevő olyan másolatot szeretne, amelyben a méretezés rejtve van, mielőtt egy beszállítóhoz kerül. A PDFium az opcionális tartalmat helyesen rajzolja, de a nyilvános ABI-jában nincs függvény az OCG-k felsorolására, konfiguráció választására vagy rétegállapot váltására. Így leereszkedik az objektumszintre, szerkeszti az /OCProperties-t, ment, újratölt, és a réteg még mindig ott van. Az ok a PDFium láthatósági logikája, és érdemes megérteni, mielőtt bármely bájthoz nyúlna

Miért nem változtatja meg a /ON és /OFF szerkesztése, amit a PDFium rajzol?

A konfigurációs szótár /ON és /OFF tömbjeinek szerkesztése önmagában nem elég, mert a PDFium hagyja, hogy az OCG saját /Usage szótárában lévő explicit állapot felülírja ezeket a tömböket, egy /AS automatikus állapotszabály pedig mindkettőt felülírhatja. Az ISO 32000-1 §8.11.4 a konfigurációkat és a használati szótárakat külön mechanizmusokként írja le; a PDFium rajzolója egyetlen döntéssé olvasztja őket össze, és az InspectOptionalContent ebben a sorrendben reprodukálja:

  • Induljon a konfiguráció /BaseState-éből, ahol a /ON és az /Unchanged egyaránt láthatónak számít, és csak a /OFF rejt
  • Alkalmazza a konfiguráció /ON tömbjét, majd a /OFF tömbjét, tehát mindkettőben szereplő csoport rejtve végzi
  • Alkalmazza a csoport explicit Usage állapotát a kért használatra, például /Usage << /View << /ViewState /OFF >> >>, amely felülír mindent, ami fentebb áll
  • Azon csoportot, amelynek /Intent-jében sincs sem /View, sem /All, tekintse láthatónak, hiszen nem vesz részt a nézet-szándékú láthatóságban
  • Végül futtassa a kiválasztott konfiguráció /AS tömbjét, amelynek az egyező eseményre vonatkozó bejegyzései beállítják az általuk felsorolt csoportok állapotát
Az ötlépéses láthatósági döntés, amelyet a PDFium Component minden PDF opcionális tartalomcsoportra lejátszik Delphiben: a BaseState adja a kiindulást, a konfiguráció ON és OFF tömbjei sorrendben alkalmazódnak, egy explicit Usage ViewState vagy PrintState bejegyzés felülírja mindkettőt, az Intent nélküli részvétel láthatónak számít, az AS tömb pedig utoljára fut
Az ON és OFF tömbök szerkesztése nem elég, mert a PDFium a BaseState-et, mindkét tömböt, a csoport Usage állapotát és végül az AS automatikus állapotszabályait egyetlen verdiktbe olvasztja, amelyet az InspectOptionalContent lépésenként reprodukál

A harmadik lépés az, amely megégeti az embereket. Egy layout eszköz által mentett fájl gyakran /ViewState /ON-t hordoz minden OCG-n, és a PDFium ekkor figyelmen kívül hagyja az Ön gondosan szerkesztett /OFF tömbjét: a mentés sikerül, a fájl tisztán újranyílik, és a réteg mégis fest. Print és Export esetén az OcExplicitUsageState előbb a PrintState-et vagy az ExportState-et olvassa, és a ViewState-re esik vissza, ha a konkrét bejegyzés hiányzik, tehát egy magányos ViewState /ON a nyomtatáshoz is rögzíti a réteget. Az OCMD-re (§8.11.2.2) hivatkozó megjelölt tartalmat ezután ezekhez a csoportonkénti eredményekhez mérnek, a /P szabály által vagy, ha jelen van, a /VE láthatósági kifejezés által

Hogyan sorolhatja fel azokat a rétegeket, amelyeket a PDFium ténylegesen mutat?

A TPdf.InspectOptionalContent egy TPdfOptionalContentInventory-t ad vissza, amelynek Groups tömbje mindegyik OCG-hez hordozza az objektumszámát, nevét, szándékait, a három Usage állapotot, a nyelvet, a zoom tartományt, a Locked jelzőt, a rádiócsoport indexét és a számolt EffectiveVisible-t. A metódus előbb megkéri a PDFiumot, hogy mentse az aktuális, memóriabeli dokumentumot, kibontja az objektumfolyamokat, és átvizsgálja az eredményt, tehát a munkamenet korábbi szerkesztései tükröződnek. A 0-s konfigurációs index mindig az alapértelmezett /D szótár, a /Configs bejegyzései pedig az 1-es indextől következnek; a -1 alapértelmezett argumentum a 0-s indexet választja. Egy /OCProperties nélküli dokumentum esetén a metódus False-szal tér vissza, az ok az ErrorMessage-ben, kivétel dobása nélkül

procedure TFormMain.ListLayers;
var
  Inv: TPdfOptionalContentInventory;
  G: TPdfOptionalContentGroup;
begin
  // A Usage alapértelmezése ocuView; a -1 a 0-s konfigurációt, a /D szótárt választja
  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;

A Memberships tömb minden OCMD-et jelent a Policy-jével (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), a nyers VisibilityExpression szöveggel és a saját EffectiveVisible-jével. Néhány szélső szabály szándékos. A /P alapértelmezése /AnyOn, és egy csoport nélküli OCMD láthatónak számít. Egy olyan objektumszámra való hivatkozás, amely nem ismert OCG, láthatónak kezelik ahelyett, hogy az egész kifejezést buktatná. A /VE kiértékelés 32-es beágyazási mélységnél megáll, és mindent mélyebbet rejtettként kezel, ami megakadályozza, hogy egy ellenséges vagy önmagára hivatkozó kifejezés a vizsgálatot stack overflow-vá alakítsa

Új rétegállapot írása a SaveAsOptionalContentConfigured-dzsel

A TPdf.SaveAsOptionalContentConfigured egy TPdfOptionalContentStateChange rekordokból álló tömböt vesz (csoportobjektumszám plusz Visible), és olyan dokumentumot ír, amelyben a kiválasztott konfiguráció pontosan azt az állapotot adja. A kiválasztott konfiguráció /BaseState /ON-t kap, plusz teljes /ON és /OFF tömböket, amelyek minden csoportot lefednek, és minden olyan OCG, amelynek már van Usage szótára, explicit ViewState-t (vagy PrintState-et / ExportState-et, az Options.Usage-ot követve) kap az új állapotához illesztve. TPdfOptionalContentConfigureOptions.Default esetén a kiválasztott konfiguráció /AS kulcsa eltávolításra kerül, hogy egy nyitó, nyomtató vagy exportáló esemény ne kapcsolhassa vissza a rétegeket

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;

  // 0-s konfiguráció, ocuView, DisableAutomaticState és 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;

Az írási út a PDFium saját mentett kimenetét tartja bájtonkénti előtagként, és csak az újraírt konfigurációtulajdonost és az Usage szótárakat hordozó OCG objektumokat fűzi hozzá, majd egy új xref szekciót és trailert. Mielőtt egyetlen bájt elérné a célját, az eredmény egy külön TPdf-ben újranyílik a szigorú betöltési szabályzattal, és a metódus elbukik, ha a kereszthivatkozási tábla nem érvényesíthető. A fájl túlterhelése még egy lépéssel megy tovább: a cél mellett egy ideiglenes fájlba ír, és a célt csak az ellenőrzés sikere után cseréli, tehát egy elutasított frissítés soha nem hagy félreírt rajzot maga után. Ugyanez az ellenőrzött növekményes revíziós megközelítés, amelyet a PDF névfa- és számfaszerkesztő a PDFium Component-ben használ

Hogyan írja a PDFium Component SaveAsOptionalContentConfigured-je a rétegekkel kapcsolt Delphi PDF-et: az állapotváltozások és az opciók bemennek, a kiválasztott konfiguráció teljes ON és OFF tömbökkel és Usage állapotokkal íródik újra, az ellenőrzött növekményes revízió hozzáfűződik, és egy szigorú újranyitásnak érvényesítenie kell, mielőtt bármi íródna
A konfigurált mentés a PDFium saját újramentését tartja bájt-előtagként, hozzáfűzi az újraírt konfigurációtulajdonost meg egy új xref szekciót, és az eredményt külön TPdf-ben nyitja újra, mielőtt a célhoz hozzányúlna

Mire nem hajlandó a konfigurált mentés?

A konfigurált mentés minden olyan változást megtagad, amelyet maga a dokumentum tilt, vagy amelyet nem tud biztonságosan ábrázolni, és minden megtagadás a cél megérintése előtt történik. Egy, az /OCGs-ben nem szereplő objektumszám egyenesen elbukik. A konfiguráció /Locked tömbjében szereplő csoport megváltoztatása elbukik, bár az aktuális értékének újramondása megengedett. Bekapcsolt EnforceRadioGroups esetén minden olyan /RBGroups készletet elutasítanak, amely egyetlen látható tagnál többel végezne, ahelyett hogy a többieket csendben kikapcsolnák. A titkosított dokumentumok elutasításra kerülnek, mert a nyílt szöveges növekményes objektumok nem hordozhatják az aktív biztonsági kezelőt. Az aláírt dokumentumok EPdfError-t dobnak, hacsak AllowSignedDocument = True-t nem ad át, hiszen az, ami egy oldalon látszik, megváltoztathatja az aláírás lefedettségét vagy egy tanúsítási szabályzatot

Az elutasítási kapuk, amelyeket a SaveAsOptionalContentConfigured alkalmaz a PDFium Component-ben, mielőtt konfigurált Delphi PDF-et írna: az OCG-kon kívüli objektumszám elbukik, a zárolt csoportok elbuknak, az egyetlen látható tagnál többet hordozó RBGroups készletek elutasításra kerülnek, a titkosított dokumentumok nem hordozhatnak nyílt szöveges növekményes objektumokat, az aláírt fájlok pedig AllowSignedDocument-et követelnek
Minden megtagadás a cél megérintése előtt történik, és a hiba oka a Report.ErrorMessage-ben landol, félreírt rajzot hátrahagyni pedig nem hagy
function TFormMain.SavePrintPreset(Target: TStream;
  const Changes: TPdfOptionalContentStateChanges): Boolean;
var
  Options: TPdfOptionalContentConfigureOptions;
  Report: TPdfOptionalContentConfigureReport;
begin
  Options := TPdfOptionalContentConfigureOptions.Default;
  Options.Usage := ocuPrint;          // /Print << /PrintState ... >>-t ír
  Options.ConfigurationIndex := 1;    // a /Configs első bejegyzése, nem a /D
  try
    Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
      Report);                        // az AllowSignedDocument False marad
    if not Result then
      ShowMessage(Report.ErrorMessage);
  except
    on E: EPdfError do
    begin
      ShowMessage(E.Message);         // aláírt fájl: semmi nem íródik a Target-be
      Result := False;
    end;
  end;
end;

Ismerje meg a kompromisszumokat, mielőtt kötegelt feladatba kötné. A hozzáfűzött revízió a PDFium teljes újramentésén ül, nem az Ön eredeti fájljának bájtjain, és pontosan ezért kíván az aláírt bemenet explicit beleegyezést. Az újraírás a kiválasztott konfigurációt /BaseState /ON-ra is normalizálja, tehát egy szerző /Unchanged vagy /OFF bázisát azonos eredményű láthatósággal járó explicit tömbök váltják fel. Az /AS eldobása eltávolítja a csak nyomtatásra vonatkozó trükköket, például egy vízjelet hordozó réteget, amely csak papíron jelenik meg; ha ezeket a szabályokat meg szeretné tartani, állítsa a DisableAutomaticState-et False-ra, vállalva, hogy azok felülírhatják a kért állapotot az adott eseményre. Plusz oldalon a PDF/A-2 (ISO 19005-2 6.9 szakasz) és a PDF/UA (ISO 14289-1 7.10 szakasz) egyaránt tiltja az /AS-t a konfigurációs szótárakban, tehát az alapértelmezett kimenet egy problémát előz meg, amelyet egyébként a PDF/A preflight validáció a PDFium Component-tel jelentene

Hol ül a rétegvezérlés egy Delphi PDF nézegetőben

Egy nézegetőben a rétegvezérlés egy jelölőnégyzetlista, amelyet a leltár hajt, plusz a mentett eredmény újratöltése. Töltse fel a listát a Groups-ból, tiltsa le a Locked bejegyzéseket, az azonos RadioGroupIndex-et hordozó tagokat tekintse kölcsönösen kizárónak, és alkalmazáskor írjon egy TMemoryStream-be, majd töltse vissza azt a streamet a TPdf-be, hogy a nézet az új állapotot fesse. A TPdf és a TPdfView közti vezetékezést a funkciókban gazdag PDF nézegető építése PDFium VCL-lel Delphiben cikk tárgyalja. Licencelés, próbaletöltések és a funkciókészlet többi része a PDFium Component Delphihez termékoldalon érhető el