PDFium Component stuurt PDF optional content-lagen (OCGs) in Delphi aan via twee TPdf-methoden: InspectOptionalContent zet elke laag op een rijtje samen met de zichtbaarheid die PDFium werkelijk rendert, en SaveAsOptionalContentConfigured schrijft een geverifieerde kopie waarin de lagen die u kiest aan of uit staan. De tweede methode neutraliseert bovendien de Usage- en /AS-regels die uw bewerking anders geruisloos ongedaan zouden maken. Beide werken op het document dat al in TPdf open staat, dus er is geen tweede parser die synchroon moet blijven met wat de viewer toont
Het verzoek komt meestal van een CAD- of GIS-huis: de tekeningenset gaat de deur uit met maatlijnen, annotaties en een titelblok op aparte lagen, en de klant wil een kopie met de maatlijnen verborgen voordat die naar een leverancier gaat. PDFium rendert optional content correct, maar zijn publieke ABI heeft geen functie om OCGs op te sommen, een configuratie te kiezen of een laagstatus om te zetten. Dus u daalt af naar objectniveau, bewerkt /OCProperties, slaat op, laadt opnieuw, en de laag staat er nog steeds. De reden is de zichtbaarheidslogica van PDFium, en die is het waard te begrijpen voordat u ook maar één byte aanraakt
Waarom verandert het bewerken van /ON en /OFF niet wat PDFium rendert?
Het bewerken van de arrays /ON en /OFF van het configuratiewoordenboek is niet genoeg, want PDFium laat een expliciete status in het eigen /Usage-woordenboek van de OCG winnen van die arrays, en een auto-state-regel /AS kan daarna beide overrulen. ISO 32000-1 §8.11.4 beschrijft configuraties en usage-woordenboeken als aparte mechanismen; de renderer van PDFium vouwt ze samen tot één besluit, en InspectOptionalContent speelt het in deze volgorde na:
- Begin bij de
/BaseStatevan de configuratie, waar/ONen/Unchangedbeide als zichtbaar tellen en alleen/OFFverbergt - Pas de array
/ONvan de configuratie toe, daarna zijn/OFF, zodat een groep die in beide staat uiteindelijk verborgen is - Pas de expliciete Usage-status van de groep toe voor de gevraagde usage, zoals
/Usage << /View << /ViewState /OFF >> >>, die alles hierboven overschrijft - Beschouw een groep waarvan
/Intentnoch/Viewnoch/Allbevat als zichtbaar, want hij doet niet mee aan view-intent-zichtbaarheid - Draai ten slotte de array
/ASvan de geselecteerde configuratie, waarvan de invoeren voor de matchende event de status bepalen van de groepen die ze noemen
De derde stap is degene waar mensen op stuklopen. Een bestand dat een layout-tool opslaat draagt vaak op elke OCG een /ViewState /ON, en PDFium negeert dan uw zorgvuldig bewerkte array /OFF: het opslaan slaagt, het bestand heropent netjes, en de laag tekent gewoon. Voor Print en Export leest OcExplicitUsageState eerst PrintState of ExportState en valt hij terug op ViewState zodra de specifieke invoer ontbreekt, dus een eenzame ViewState /ON zet de laag ook voor het printen vast. Marked content die naar een OCMD verwijst (§8.11.2.2) wordt daarna tegen deze resultaten per groep opgelost, via het /P-beleid of, als het er is, de zichtbaarheidsexpressie /VE
Hoe zet u de lagen op een rij die PDFium werkelijk toont?
TPdf.InspectOptionalContent geeft een TPdfOptionalContentInventory terug waarvan de array Groups per OCG het objectnummer, de naam, de intents, de drie Usage-statussen, de taal, het zoombereik, de vlag Locked, de radiogroepindex en de berekende EffectiveVisible meedraagt. De methode laat PDFium eerst het huidige in-memory document opslaan, expandeert objectstreams en scant het resultaat, dus bewerkingen die eerder in de sessie zijn gedaan zijn verwerkt. Configuratie-index 0 is altijd het standaardwoordenboek /D en de invoeren van /Configs volgen vanaf index 1; het standaardargument van -1 kiest index 0. Een document zonder /OCProperties laat de methode False teruggeven met de reden in ErrorMessage in plaats van een exceptie
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Usage staat standaard op ocuView; -1 kiest configuratie 0, het /D-woordenboek
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;
De array Memberships meldt elke OCMD met zijn Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), de rauwe tekst VisibilityExpression en zijn eigen EffectiveVisible. Een paar randregels zijn bewust zo. /P staat standaard op /AnyOn, en een OCMD zonder groepen telt als zichtbaar. Een verwijzing naar een objectnummer dat geen bekende OCG is, wordt als zichtbaar behandeld in plaats van de hele expressie te laten falen. De evaluatie van /VE stopt bij een nestdiepte van 32 en behandelt alles dieper als verborgen, wat voorkomt dat een vijandige of zelfverwijzende expressie inspectie laat omslaan in een stack overflow
Een nieuwe laagstatus schrijven met SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured neemt een array van records TPdfOptionalContentStateChange (objectnummer van de groep plus Visible) en schrijft een document waarin de geselecteerde configuratie precies die status oplevert. De geselecteerde configuratie krijgt /BaseState /ON plus volledige arrays /ON en /OFF die elke groep bestrijken, en elke OCG die al een Usage-woordenboek heeft krijgt een expliciete ViewState (of PrintState / ExportState, volgens Options.Usage) die bij zijn nieuwe status past. Met TPdfOptionalContentConfigureOptions.Default wordt de sleutel /AS van de geselecteerde configuratie verwijderd, zodat een open-, print- of export-event de lagen niet terug kan omgooien
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;
// Configuratie 0, ocuView, DisableAutomaticState en 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;
Het schrijfpad houdt de eigen opgeslagen uitvoer van PDFium aan als byte-voor-byte-prefix en voegt alleen de herschreven configuratie-eigenaar en de OCG-objecten met Usage-woordenboeken toe, gevolgd door een nieuwe xref-sectie en trailer. Voordat er ook maar één byte uw bestemming bereikt, wordt het resultaat in een aparte TPdf heropend onder het strikte laadbeleid, en de methode faalt als de kruisverwijzingstabel niet valideert. De bestandsoverload gaat nog een stap verder: die schrijft naar een tijdelijk bestand naast het doel en vervangt het doel pas als de verificatie slaagt, dus een geweigerde update laat nooit een halfgeschreven tekening achter. Het is dezelfde aanpak van geverifieerde incrementele revisie die de PDF name tree- en number tree-editor in PDFium Component gebruikt
Wat weigert de geconfigureerde opslag te doen?
De geconfigureerde opslag weigert elke wijziging die het document zelf verbiedt of niet veilig kan weergeven, en elke weigering gebeurt voordat de bestemming wordt aangeraakt. Een objectnummer dat niet in /OCGs staat faalt ronduit. Het wijzigen van een groep die in de array /Locked van de configuratie staat faalt, al mag het herbevestigen van zijn huidige waarde wel. Met EnforceRadioGroups aan wordt elke /RBGroups-verzameling die met meer dan één zichtbaar lid zou eindigen geweigerd in plaats van de anderen geruisloos uit te zetten. Versleutelde documenten worden geweigerd omdat plaintext-incrementele objecten de actieve security handler niet kunnen meedragen. Ondertekende documenten gooien EPdfError tenzij u AllowSignedDocument = True doorgeeft, want veranderen wat een pagina toont kan handtekeningdekking of een certificeringsbeleid breken
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // schrijft /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // eerste invoer van /Configs, niet /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // AllowSignedDocument blijft False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // ondertekend bestand: niets naar Target geschreven
Result := False;
end;
end;
end;
Ken de afwegingen voordat u dit in een batchtaak inbouwt. De toegevoegde revisie rust bovenop de volledige hersave van PDFium, niet op de oorspronkelijke bestandsbytes, en dat is precies waarom ondertekende invoer expliciete toestemming nodig heeft. De herschrijving normaliseert de geselecteerde configuratie bovendien naar /BaseState /ON, dus een baseline /Unchanged of /OFF van de auteur wordt vervangen door expliciete arrays met dezelfde resulterende zichtbaarheid. Het laten vallen van /AS schrapt print-only trucs zoals een watermerklaag die alleen op papier verschijnt; zet DisableAutomaticState op False om die regels te houden, accepterend dat ze uw gevraagde status voor dat event kunnen overrulen. Aan de positieve kant: PDF/A-2 (ISO 19005-2 clausule 6.9) en PDF/UA (ISO 14289-1 clausule 7.10) verbieden beide /AS in configuratiewoordenboeken, dus de standaarduitvoer schrapt één probleem dat uw PDF/A-preflightvalidatie met PDFium Component anders zou melden
Waar laagbeheer past in een Delphi-PDF-viewer
In een viewer is laagbeheer een checklist aangedreven door de inventaris plus een heropening van het opgeslagen resultaat. Vul de checklist vanuit Groups, maak de invoeren die Locked zijn onbruikbaar, behandel leden die een RadioGroupIndex delen als wederzijds uitsluitend, en schrijf bij toepassen naar een TMemoryStream en laad die stream terug in TPdf zodat de view de nieuwe status tekent. De samenhang tussen TPdf en TPdfView staat beschreven in een veelzijdige PDF-viewer bouwen met PDFium VCL in Delphi. Licenties, trial-downloads en de rest van de featureset staan op de productpagina van PDFium Component voor Delphi