PDFium Component ovládá PDF optional content vrstvy (OCG) v Delphi přes dvě metody TPdf: InspectOptionalContent vypíše každou vrstvu i s viditelností, kterou PDFium doopravdy vyrenderuje, a SaveAsOptionalContentConfigured zapíše ověřenou kopii, ve které jsou vrstvy dle vaší volby zapnuté nebo vypnuté. Druhá metoda navíc neutralizuje pravidla Usage a /AS, která by jinak vaši editaci potichu zrušila. Obojí pracuje na dokumentu už otevřeném v TPdf, takže tu není druhý parser, který by se udržoval ve shodě s tím, co prohlížeč ukazuje
Požadavek typicky přijde z CAD nebo GIS dílny: výkresová sada odjíždí s rozměry, anotacemi a title blockem na samostatných vrstvách a zákazník chce kopii se skrytými rozměry, než poputuje k dodavateli. PDFium renderuje optional content správně, ale jeho veřejné ABI nemá žádnou funkci na enumeraci OCG, výběr konfigurace nebo překlopení stavu vrstvy. Takže sestoupíte na úroveň objektů, editujete /OCProperties, uložíte, načtete znovu — a vrstva je pořád tam. Příčinou je viditelnostní logika PDFium a stojí za to jí porozumět, než sahate na jakékoli byty
Proč editace /ON a /OFF nezmění, co PDFium renderuje?
Editace polí /ON a /OFF konfiguračního slovníku nestačí, protože PDFium nechá explicitní stav uvnitř vlastního slovníku /Usage OCG přebít ta pole a pravidlo auto-stavu /AS pak může přebít obojí. ISO 32000-1 §8.11.4 popisuje konfigurace a usage slovníky jako oddělené mechanismy; renderer PDFium je slepí do jediného rozhodnutí a InspectOptionalContent ho reprodukuje v tomhle pořadí:
- Vyjděte z
/BaseStatekonfigurace, kde/ONi/Unchangedpočítají jako viditelné a jen/OFFschovává - Aplikujte pole
/ONkonfigurace, pak její pole/OFF, takže skupina uvedená v obou skončí skrytá - Aplikujte explicitní Usage stav skupiny pro požadované použití, jako
/Usage << /View << /ViewState /OFF >> >>, který přebíjí všechno výše - Skupinu, jejíž
/Intentneobsahuje ani/View, ani/All, berte jako viditelnou, protože se na viditelnosti podle view intentu nepodílí - Nakonec pusťte pole
/ASvybrané konfigurace, jehož položky pro odpovídající event nastaví stav skupin, které vyjmenovávají
Třetí krok je ten, který pálí. Soubor uložený layoutovým nástrojem často nese /ViewState /ON na každém OCG a PDFium pak ignoruje vaše pečlivě editované pole /OFF: uložení uspěje, soubor se čistě znovu otevře a vrstva se pořád maluje. Pro Print a Export čte OcExplicitUsageState nejdřív PrintState nebo ExportState a na ViewState spadne, když konkrétní položka chybí, takže osamocené ViewState /ON přichytí vrstvu i pro tisk. Marked content odkazující na OCMD (§8.11.2.2) se pak rozliší proti těmto per-group výsledkům, přes politiku /P nebo, je-li přítomná, viditelnostní výraz /VE
Jak vypíšete vrstvy, které PDFium doopravdy ukáže?
TPdf.InspectOptionalContent vrací TPdfOptionalContentInventory, jehož pole Groups nese objektové číslo každého OCG, název, intenty, tři Usage stavy, jazyk, zoom range, flag Locked, index radio skupiny a spočítané EffectiveVisible. Metoda nejdřív nechá PDFium uložit aktuální dokument v paměti, expanduje object streamy a proskenuje výsledek, takže se v ní odrážejí i editace provedené dřív v session. Konfigurační index 0 je vždy defaultní slovník /D a položky /Configs následují od indexu 1; defaultní argument -1 vybírá index 0. Dokument bez /OCProperties donutí metodu vrátit False s důvodem v ErrorMessage místo vyhození výjimky
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Usage defaultně ocuView; -1 vybere konfiguraci 0, slovník /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;
Pole Memberships hlásí každý OCMD s jeho Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), syrovým textem VisibilityExpression a vlastním EffectiveVisible. Pár hranických pravidel je záměrných. /P defaultuje na /AnyOn a OCMD bez skupin se počítá jako viditelný. Reference na objektové číslo, které není známé OCG, se bere jako viditelná místo toho, aby shodila celý výraz. Vyhodnocování /VE končí na zanoření 32 a všechno hlouběji bere jako skryté, takže nepřátelský nebo se odkazující výraz nezvrátí inspekci v stack overflow
Zápis nového stavu vrstvy přes SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured bere pole záznamů TPdfOptionalContentStateChange (objektové číslo skupiny plus Visible) a zapíše dokument, ve kterém vybraná konfigurace produkuje přesně ten stav. Vybraná konfigurace dostane /BaseState /ON plus kompletní pole /ON a /OFF pokrývající každou skupinu a každé OCG, které už má Usage slovník, dostane explicitní ViewState (nebo PrintState / ExportState dle Options.Usage) odpovídající svému novému stavu. S TPdfOptionalContentConfigureOptions.Default se klíč /AS vybrané konfigurace odstraní, takže event otevření, tisku nebo exportu nemůže vrstvy překlopit zpátky
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;
// Konfigurace 0, ocuView, DisableAutomaticState a 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;
Zápisová cesta si drží vlastní uložený výstup PDFium jako bajt za bajtem identickou předponu a přilepí jen přepsaného vlastníka konfigurace a OCG objekty nesoucí Usage slovníky, za nimi novou xref sekci a trailer. Než jediný byte dorazí do vašeho cíle, výsledek se znovu otevře v odděleném TPdf pod přísnou load politikou a metoda selže, když tabulka cross-referencí nezvaliduje. File overload jde krok dál: píše do dočasného souboru vedle cíle a cíli nahradí až po úspěšné verifikaci, takže odmítnutá aktualizace nikdy nenechá za sebou napůl napsaný výkres. Je to tentýž ověřený přístup inkrementální revize, který používá editor PDF name tree a number tree v PDFium Component
Co konfigurované uložení odmítá?
Konfigurované uložení odmítne každou změnu, kterou dokument sám zakazuje nebo ji nedokáže bezpečně reprezentovat, a každé odmítnutí nastane dřív, než se cíl dotkne. Objektové číslo, které není v /OCGs, selže rovnou. Změna skupiny uvedené v poli /Locked konfigurace selže, i když restatement její aktuální hodnoty je povolený. Se zapnutým EnforceRadioGroups se odmítne každá sada /RBGroups, která by skončila s víc než jedním viditelným členem, místo aby potichu vypla ostatní. Šifrované dokumenty se odmítají, protože inkrementální objekty v plaintextu nemohou nést aktivní security handler. Podepsané dokumenty vyhodí EPdfError, pokud nepředáte AllowSignedDocument = True, protože změna toho, co stránka ukazuje, může rozbít pokrytí podpisu nebo certification politiku
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // zapíše /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // první položka /Configs, ne /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // AllowSignedDocument zůstává False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // podepsaný soubor: do Target se nic nezapisuje
Result := False;
end;
end;
end;
Poznejte trade-offy, než tohle zapojíte do batch jobu. Přilepená revize sedí na vršku kompletního re-save PDFium, ne vašich původních bytů souboru — a přesně proto potřebuje podepsaný vstup explicitní souhlas. Přepis taky normalizuje vybranou konfiguraci na /BaseState /ON, takže autorův baseline /Unchanged nebo /OFF vystřídají explicitní pole se stejnou výslednou viditelností. Odstranění /AS zruší tiskové triky jako vrstvu s vodoznakem, která se objeví jen na papíře; nastavte DisableAutomaticState na False, abyste ta pravidla nechali, s přijetím, že pro ten event mohou přebít váš požadovaný stav. Na druhou stranu PDF/A-2 (ISO 19005-2 clause 6.9) i PDF/UA (ISO 14289-1 clause 7.10) zakazují /AS v konfiguračních slovnících, takže defaultní výstup odstraní jeden nález, který by vaše PDF/A preflight validace s PDFium Component jinak nahlásila
Kam se kontrola vrstev hodí v Delphi PDF prohlížeči
V prohlížeči je kontrola vrstev checklist řízený inventářem plus znovunačtením uloženého výsledku. Naplňte checklist z Groups, zakažte položky, které jsou Locked, berte členy sdílející RadioGroupIndex jako vzájemně se vylučující a při aplikaci zapište do TMemoryStream a ten stream načtěte zpět do TPdf, aby view namaloval nový stav. Propojení mezi TPdf a TPdfView rozebírá článek o stavbě bohatého PDF prohlížeče s PDFium VCL v Delphi. Licencování, trial download a zbytek funkcionality najdete na produktové stránce PDFium Component pro Delphi