PDFium Component styrer PDF optional content-lag (OCGs) i Delphi gennem to TPdf-metoder: InspectOptionalContent lister hvert lag sammen med den synlighed, PDFium reelt renderer, og SaveAsOptionalContentConfigured skriver en verificeret kopi, i hvilken de lag, du vælger, er slået til eller fra. Den anden metode neutraliserer også Usage- og /AS-reglerne, som ellers i det stille ville fortryde din redigering. Begge virker på dokumentet, der allerede er åbent i TPdf, så der er ingen anden parser at holde i sync med, hvad viewer'en viser
Forespørgslen kommer som regel fra et CAD- eller GIS-hus: tegningssættet sejler med mål, anmærkninger og en titelblok på separate lag, og kunden vil have en kopi med målene skjulte, før den går til en leverandør. PDFium renderer optional content korrekt, men dets offentlige ABI har ingen funktion til at enumerere OCGs, vælge en konfiguration eller vende en lag-tilstand. Så man går ned på objektniveau, redigerer /OCProperties, gemmer, genindlæser, og laget er der stadig. Grunden er PDFiums synlighedslogik, og den er værd at forstå, før man rører nogen bytes
Hvorfor ændrer redigering af /ON og /OFF ikke, hvad PDFium renderer?
At redigere /ON- og /OFF-arrays i konfigurationsdictionaryen er ikke nok, fordi PDFium lader en eksplicit tilstand inde i OCG'ens egen /Usage-dictionary vinde over de arrays, og en /AS auto-state-regel kan derefter overskrive begge. ISO 32000-1 §8.11.4 beskriver konfigurationer og usage dictionaries som separate mekanismer; PDFiums renderer folder dem sammen til én enkelt beslutning, og InspectOptionalContent reproducerer den i denne rækkefølge:
- Start fra konfigurationens
/BaseState, hvor/ONog/Unchangedbegge tæller som synlige, og kun/OFFskjuler - Anvend konfigurationens
/ON-array, derefter dens/OFF-array, så en gruppe opført i begge ender skjult - Anvend gruppens eksplicitte Usage-tilstand for den efterspurgte usage, såsom
/Usage << /View << /ViewState /OFF >> >>, som overskriver alt ovenfor - Behandl en gruppe, hvis
/Intenthverken indeholder/Vieweller/All, som synlig, da den ikke deltager i view-intent synlighed - Kør til sidst den valgte konfigurations
/AS-array, hvis entries for det matchende event sætter tilstanden for de grupper, de lister
Tredje trin er det, der brænder folk. En fil gemt af et layoutværktøj bærer ofte /ViewState /ON på hver OCG, og PDFium ignorerer så dit omhyggeligt redigerede /OFF-array: gemningen lykkes, filen genåbner rent, og laget maler stadig. For Print og Export læser OcExplicitUsageState PrintState eller ExportState først og falder tilbage til ViewState, når den specifikke entry mangler, så en enlig ViewState /ON fastholder laget til udskrivning også. Marked content, der refererer en OCMD (§8.11.2.2), resolves derefter mod disse pr.-gruppe-resultater, gennem /P-politikken eller, når til stede, /VE-synlighedsudtrykket
Hvordan lister man de lag, PDFium reelt vil vise?
TPdf.InspectOptionalContent returnerer et TPdfOptionalContentInventory, hvis Groups-array bærer hver OCG's objektnummer, navn, intents, de tre Usage-tilstande, sprog, zoom-område, Locked-flag, radio-gruppe-indeks og den beregnede EffectiveVisible. Metoden får først PDFium til at gemme det aktuelle in-memory dokument, ekspanderer object streams og scanner resultatet, så redigeringer foretaget tidligere i sessionen afspejles. Konfigurationsindeks 0 er altid standard /D-dictionaryen, og /Configs-entryerne følger fra indeks 1; standardargumentet -1 vælger indeks 0. Et dokument uden /OCProperties får metoden til at returnere False med årsagen i ErrorMessage frem for at rejse
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Usage står som standard til ocuView; -1 vælger konfiguration 0, /D-dictionaryen
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;
Memberships-arrayet rapporterer hver OCMD med dens Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), den rå VisibilityExpression-tekst og dens egen EffectiveVisible. Et par kantregler er bevidste. /P står som standard til /AnyOn, og en OCMD uden grupper tæller som synlig. En reference til et objektnummer, der ikke er en kendt OCG, behandles som synlig frem for at få hele udtrykket til at fejle. /VE-evalueringen stopper ved en nesting-dybde på 32 og behandler alt dybere som skjult, hvilket forhindrer et fjendtligt eller selvrefererende udtryk i at gøre inspektionen til en stack overflow
At skrive en ny lag-tilstand med SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured tager et array af TPdfOptionalContentStateChange-records (gruppeobjektnummer plus Visible) og skriver et dokument, i hvilket den valgte konfiguration producerer præcis den tilstand. Den valgte konfiguration får /BaseState /ON plus komplette /ON- og /OFF-arrays, der dækker hver gruppe, og hver OCG, der allerede har en Usage-dictionary, modtager en eksplicit ViewState (eller PrintState / ExportState, følgende Options.Usage), der matcher dens nye tilstand. Med TPdfOptionalContentConfigureOptions.Default fjernes /AS-nøglen fra den valgte konfiguration, så et open-, print- eller export-event ikke kan vende lagene tilbage
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;
// Konfiguration 0, ocuView, DisableAutomaticState og 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;
Skrivestien beholder PDFiums egen gemte output som byte-for-byte-præfiks og føjer kun den omskrevne configuration owner og de OCG-objekter, der bærer Usage-dictionaries, efterfulgt af en ny xref-sektion og trailer. Før en eneste byte når din destination, genåbnes resultatet i en separat TPdf under den strenge load-politik, og metoden fejler, hvis cross-reference-tabellen ikke validerer. Fil-overloaden går et skridt videre: den skriver til en midlertidig fil ved siden af målet og erstatter målet først, efter verifikationen lykkes, så en afvist opdatering aldrig efterlader en halvfærdig tegning. Det er samme verificerede inkrementelle revisionstilgang, som PDF name tree- og number tree-editoren i PDFium Component bruger
Hvad nægter den konfigurerede gemning at gøre?
Den konfigurerede gemning nægter enhver ændring, dokumentet selv forbyder eller ikke kan repræsentere sikkert, og hver afvisning sker, før destinationen røres. Et objektnummer, der ikke er i /OCGs, fejler kategorisk. At ændre en gruppe opført i konfigurationens /Locked-array fejler, selvom det er tilladt at gentage dens aktuelle værdi. Med EnforceRadioGroups slået til afvises ethvert /RBGroups-sæt, der ville ende med mere end ét synligt medlem, i stedet for stille at slå de andre fra. Krypterede dokumenter afvises, fordi klartekst-inkrementelle objekter ikke kan bære den aktive security handler. Signerede dokumenter rejser EPdfError, medmindre du giver AllowSignedDocument = True med, da det at ændre, hvad en side viser, kan knække signaturdækning eller en certificeringspolitik
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // skriver /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // første entry af /Configs, ikke /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // AllowSignedDocument forbliver False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // signeret fil: intet skrevet til Target
Result := False;
end;
end;
end;
Kend afvejningerne, før du kobler dette ind i et batch-job. Den tilføjede revision sidder oven på PDFiums fulde re-gemning, ikke dine originale filbytes, hvilket er præcis, hvorfor signeret input behøver eksplicit samtykke. Omskrivningen normaliserer også den valgte konfiguration til /BaseState /ON, så en forfatters /Unchanged- eller /OFF-baseline erstattes af eksplicitte arrays med samme resulterende synlighed. At droppe /AS fjerner kun-udskrivning-tricks som et vandmærke-lag, der kun dukker op på papir; sæt DisableAutomaticState til False for at beholde de regler og acceptere, at de kan overskrive din efterspurgte tilstand for det event. På plussiden forbyder både PDF/A-2 (ISO 19005-2 clause 6.9) og PDF/UA (ISO 14289-1 clause 7.10) /AS i konfigurationsdictionaries, så standardoutput fjerner ét problem, som din PDF/A preflight-validering med PDFium Component ellers ville rapportere
Hvor lag-styring passer ind i en Delphi PDF-viewer
I en viewer er lag-styring en tjekliste drevet af inventaret plus en genindlæsning af det gemte resultat. Udfyld tjeklisten fra Groups, deaktivér de entries, der er Locked, behandl medlemmer, der deler et RadioGroupIndex, som gensidigt udelukkende, og ved anvendelse skriv til en TMemoryStream og indlæs den stream tilbage i TPdf, så visningen maler den nye tilstand. Koblingen mellem TPdf og TPdfView er dækket i at bygge en funktionsrig PDF-viewer med PDFium VCL i Delphi. Licensering, prøvedownloads og resten af featuresættet findes på PDFium Component for Delphi-produktsiden