Een tekeningenset komt binnen met maatlijnen op een eigen laag, en de voor de hand liggende manier om ze te verbergen is de referentie van die groep uit de /ON-array van de standaardconfiguratie naar /OFF te verplaatsen. Het bestand wordt opgeslagen en heropend, en de maatlijnen worden nog steeds dwars door het titelblok gedrukt. De oorzaak zit één object verderop, in de groepdictionary: /Usage << /View << /ViewState /ON >> >>, een expliciete status die de renderer vóór de configuratie-arrays leest
De status van een laag kan op meerdere plekken in een PDF staan, en een bewerking van /OCProperties raakt er maar één ervan. De PDFium-engine van Google rendert optional content correct, maar zijn publieke C-API heeft geen functie om optional content-groepen (OCGs) op te sommen, een configuratie te kiezen of een groep aan en uit te zetten. Delphi-code die lagen aanstuurt, moet de objecten zelf herschrijven en de beslisvolgorde van de renderer exact naspelen, anders meldt zijn laaglijst de ene status terwijl de pagina de andere laat zien
Waarom verandert het bewerken van /ON en /OFF niets aan wat PDFium rendert?
De arrays /ON en /OFF zijn pas de tweede van vijf stappen, en twee latere stappen kunnen ze oversturen. ISO 32000-1 §8.11.4.3 definieert de optional content-configuration-dictionary met /BaseState, /ON, /OFF, /Locked, /RBGroups en /AS, terwijl §8.11.4.4 de Usage-dictionary definieert die een OCG kan meedragen. In de specificatie beschrijft Usage waar een groep voor dient, en de auto-state-invoeren in /AS zetten die beschrijving om in ON of OFF. PDFium neemt een kortere route: als hij voor een doel rendert, wint de eigen expliciete status van de groep voor dat doel. Vouwt u dat samen, dan ziet de volgorde er zo uit:
- Begin bij de
/BaseStatevan de configuratie, waar/ONen/Unchangedallebei als zichtbaar tellen en alleen/OFFverbergt - Pas de
/ON-array toe, dan de/OFF-array, zodat een groep die in beide staat uiteindelijk verborgen is - Pas de expliciete Usage-status van de groep voor het gevraagde doel toe, zoals
/Usage << /View << /ViewState /OFF >> >>, die alles hierboven overstijgt - Beschouw een groep waarvan
/Intentnoch/Viewnoch/Allbevat als zichtbaar, want hij doet niet mee aan view-intent-zichtbaarheid - Draai de
/AS-array van de geselecteerde configuratie als laatste, waarbij invoeren voor de matchende gebeurtenis de status van de groepen die ze noemen bepalen
Stap drie verklaart het openingsbestand: sommige exporteurs schrijven /ViewState /ON in elke groep die ze maken, en vanaf dat moment verbergt geen enkele arraybewerking die lagen nog. Printen en export volgen dezelfde regel met één draai: de renderer leest eerst /PrintState of /ExportState en valt terug op /ViewState als de specifieke invoer ontbreekt. Een eenzame /ViewState /ON spijkert de laag dus ook op papier vast. Stap vier is stiller. Een groep getagd /Intent /Design valt buiten view-intent-zichtbaarheid en schildert ongeacht de arrays, terwijl een groep zonder /Intent-sleutel als /View geldt, de standaard die de specificatie geeft
Lagen en hun gerenderde status tonen met InspectOptionalContent
PDFium Component speelt die volgorde na in TPdf.InspectOptionalContent. De methode vult een TPdfOptionalContentInventory en geeft True terug bij succes; zijn parameter Usage staat standaard op ocuView en zijn parameter ConfigurationIndex op -1, wat index 0 kiest. Configuratie-index 0 is de standaard /D-dictionary, en de entries van /Configs volgen vanaf index 1 in bestandsvolgorde. Vóór de scan laat de methode PDFium het open document in het geheugen opslaan en pakt hij eventuele object streams uit, zodat bewerkingen van eerder in de sessie in het resultaat meekomen. Een bestand zonder /OCProperties, of een configuratie-index buiten het beschikbare bereik, laat de aanroep False teruggeven met de reden in ErrorMessage; voor geen van beide wordt een exception geworpen
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
Cfg: TPdfOptionalContentConfiguration;
G: TPdfOptionalContentGroup;
begin
// Usage staat standaard op ocuView; -1 kiest configuratie 0, de /D-dictionary
if not Pdf.InspectOptionalContent(Inv) then
begin
Memo1.Lines.Add('No usable layers: ' + Inv.ErrorMessage);
Exit;
end;
Cfg := Inv.Configurations[Inv.SelectedConfigurationIndex];
Memo1.Lines.Add(Format('Configuration %d "%s", %d groups, /AS present: %s',
[Inv.SelectedConfigurationIndex, string(Cfg.Name), Length(Inv.Groups),
BoolToStr(Cfg.HasAutomaticState, True)]));
for G in Inv.Groups do
Memo1.Lines.Add(Format('obj %d %-24s visible=%s locked=%s radio=%d',
[G.ObjectNumber, string(G.Name),
BoolToStr(G.EffectiveVisible, True),
BoolToStr(G.Locked, True), G.RadioGroupIndex]));
end;
Elke TPdfOptionalContentGroup draagt zijn objectnummer, naam, Intents en de drie expliciete statussen ViewState, PrintState en ExportState, elk één van ocsUnspecified, ocsOn of ocsOff. Leest een status ocsOn en zit de groep in de /OFF-array, dan heeft u de valkuil uit de openingsparagraaf te pakken. Locked en RadioGroupIndex komen uit de geselecteerde configuratie, en RadioGroupIndex is -1 voor een groep die bij geen enkele /RBGroups-verzameling hoort
De rest van de Usage-dictionary wordt gemeld zoals het bestand ze aangeeft: Language en LanguagePreferred, UserType en UserNames, PageElementSubtype, en MinimumZoom en MaximumZoom achter een HasZoomRange-vlag. EffectiveVisible hangt van geen van allen af. Zoomafhankelijke weergave is aan uw viewer. Elke TPdfOptionalContentConfiguration legt zijn naam, BaseState, arrayinhoud en RadioGroups bloot, plus OrderGroupObjectNumbers, dat /Order afvlakt tot groepsverwijzingen en de tekstlabels van geneste labelarrays overslaat
Print en export kunnen het met het scherm oneens zijn
Het argument Usage verandert het antwoord, en dat is precies het doel. Onder ocuPrint leest de inventaris /PrintState met zijn terugval op /ViewState, en alleen /AS-invoeren waarvan /Event /Print is doen mee. Een watermerklaag die alleen op papier moet verschijnen meldt zich onder ocuView dus verborgen en onder ocuPrint zichtbaar. De twee passages vergelijken vindt zulke lagen voordat een afdruk iemand verrast
procedure TFormMain.ReportPrintOnlyLayers;
var
OnScreen, OnPaper: TPdfOptionalContentInventory;
I: Integer;
begin
if not Pdf.InspectOptionalContent(OnScreen, ocuView) or
not Pdf.InspectOptionalContent(OnPaper, ocuPrint) then
Exit;
// Beide passages lezen dezelfde /OCGs-array, dus de groepsvolgorde klopt
for I := 0 to High(OnScreen.Groups) do
if OnScreen.Groups[I].EffectiveVisible <> OnPaper.Groups[I].EffectiveVisible then
Memo1.Lines.Add(Format('%s: screen=%s, print=%s',
[string(OnScreen.Groups[I].Name),
BoolToStr(OnScreen.Groups[I].EffectiveVisible, True),
BoolToStr(OnPaper.Groups[I].EffectiveVisible, True)]));
end;
Hoe worden OCMD-zichtbaarheidsuitdrukkingen geëvalueerd?
Een optional content membership dictionary (OCMD, ISO 32000-1 §8.11.2.2) koppelt een stuk inhoud via een /P-policy aan meerdere groepen, of via een /VE-visibility-expressie die groepen met /And, /Or en /Not combineert. De specificatie geeft /P een standaard van /AnyOn, laat /VE voorgaan op /OCGs en /P als hij aanwezig is, en zegt dat een OCMD zonder groepen niets doet aan zichtbaarheid
De Memberships-array van de inventaris volgt die regels tegen de per-groep resultaten van dezelfde passage, dus de usage die u vroeg loopt door. Elk lidmaatschap meldt zijn groepsobjectnummers, Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff, of ocmpUnknown voor een naam buiten die vier), de rauwe VisibilityExpression-tekst en zijn eigen EffectiveVisible. Een OCMD zonder groepen geldt als zichtbaar. Ook een verwijzing naar een objectnummer dat geen bekende OCG is, geldt als zichtbaar, zodat één losse verwijzing niet alles eromheen verbergt. /VE-evaluatie stopt op een nestingsdiepte van 32 en behandelt alles dieper als verborgen, zodat een zichzelf refererende expressie de stack niet kan overlopen. De scan somt elke OCMD op die als indirect object met /Type /OCMD is opgeslagen, object streams incluis
Een nieuwe laagstatus schrijven met SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured neemt een array van TPdfOptionalContentStateChange-records, elk met een GroupObjectNumber en een Visible-vlag. Hij schrijft een kopie waarin de geselecteerde configuratie onder de vijfstappenorder precies die status oplevert. Groepen die u niet noemen houden de zichtbaarheid die de inventaris voor hen berekende
procedure TFormMain.SaveWithoutDimensions(DimensionsObj, NotesObj: Integer;
ForPrint: Boolean);
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;
Options := TPdfOptionalContentConfigureOptions.Default;
if ForPrint then
begin
Options.Usage := ocuPrint; // schrijft /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // eerste /Configs-entry, /D blijft zoals hij was
end;
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;
De uitvoer houdt het document dat PDFium zelf opslaat als byte-voor-byte-prefix en voegt alleen de herschreven configuratie-eigenaar en de OCG-objecten die Usage-dictionaries dragen toe, gevolgd door een nieuwe xref-sectie en trailer. Voordat ook maar iets uw bestemming bereikt, wordt het resultaat in een aparte TPdf onder het strikte laadbeleid heropend, en de methode faalt als die laad niet slaagt of de cross-reference-tabel niet valideert. De bestandsoverload schrijft naar een tijdelijk bestand naast het doel en vervangt het doel pas na verificatie, dus een geweigerde update laat de vorige tekening onaangeroerd. Het is dezelfde geverifieerde incrementele-revisiebenadering die de PDF name tree- en number tree-editor in PDFium Component gebruikt. Bevestig een printpreset door de uitvoer opnieuw te laden en InspectOptionalContent(Inv, ocuPrint, 1) aan te roepen; de groepen horen dan de statussen te melden die u doorgaf
Wat weigert de geconfigureerde opslag te doen?
De geconfigureerde opslag weigert elke wijziging die het document verbiedt of niet veilig kan dragen, en elke weigering gebeurt voordat de bestemming wordt weggeschreven. De controles draaien in vaste volgorde. Een ondertekend document werpt EPdfError voordat er iets anders gebeurt, tenzij u AllowSignedDocument = True doorgeeft. Een versleuteld document wordt als volgende geweigerd, want plaintext-incrementele objecten kunnen de actieve security handler niet dragen. Daarna wordt elke gevraagde wijziging gecontroleerd: een objectnummer dat niet in /OCGs staat faalt ronduit, en een wijziging aan een groep die in de /Locked-array van de configuratie staat faalt, al zal het herstellen van zijn huidige waarde doorgaan. Ten slotte wordt, met EnforceRadioGroups aan, een /RBGroups-verzameling die met meer dan één zichtbaar lid zou eindigen geweigerd. Elke weigering behalve de signatuur geeft False terug met de reden in Report.ErrorMessage
Twee van die poorten gaan verder dan de specificatie strikt eist. §8.11.4.3 beschrijft /Locked als bescherming tegen wijzigingen via de gebruikersinterface van de viewer en beschrijft radioknopgroepen als het uitschakelen van de andere leden als er één wordt aangezet. PDFium Component past beide ook toe op programmatische wijzigingen, en bij radiogroepen weigert hij in plaats van leden uit te kiezen om uit te zetten. Gevraagd om twee leden van een wederzijds uitsluitende verzameling kan de bibliotheek niet weten welke u bedoelde, en een gok zou uw verzoek tegenspreken. Zet de anderen uit in dezelfde Changes-array en de opslag gaat door
De poort voor ondertekende documenten bestaat omdat de toegevoegde revisie bovenop de volledige hersave van PDFium zit, wat een nieuwe bytereeks is; de byte-range die een bestaande handtekening dekt, kan niet worden verondersteld te overleven. Geef AllowSignedDocument = True alleen door als het verlies van die handtekening acceptabel is
Drie neveneffecten tellen in een batchtaak. De herschrijving normaliseert de geselecteerde configuratie naar /BaseState /ON, dus een baseline /Unchanged of /OFF van de auteur wordt expliciete arrays met dezelfde resulterende zichtbaarheid. /AS laten vallen verwijdert ook print-only trucs zoals een watermerklaag die alleen op papier verschijnt; zet DisableAutomaticState op False om die regels te houden en accepteer dat ze uw gevraagde status voor hun gebeurtenis kunnen oversturen. En de arrays /ON en /OFF worden geschreven vanuit de statussen berekend voor Options.Usage, dus een printpreset die in configuratie 0 wordt opgeslagen verandert ook wat het scherm toont voor elke groep zonder Usage-dictionary. Houd printpresets in een /Configs-entry en laat /D aan het scherm. Aan de compliance-kant verbieden PDF/A-2 (ISO 19005-2 clause 6.9) en PDF/UA (ISO 14289-1 clause 7.10) allebei /AS in configuratiedictionaries, dus de standaarduitvoer ruimt één bevinding op die uw PDF/A-preflightvalidatie met PDFium Component anders zou melden
Laagwijzigingen toepassen binnen een Delphi-viewer
In een viewer is laagbeheer een checklist gebouwd vanuit de inventaris, gevolgd door een opslag naar het geheugen en een herlading. Vul de checklist vanuit Groups, schakel entries uit waarvan de Locked-vlag staat, behandel leden die een RadioGroupIndex delen als wederzijds uitsluitend, en schrijf bij toepassen naar een TMemoryStream via de stream-overload. TPdf.LoadDocument verwacht een inactieve component, dus zet hem eerst uit; de gebufferde laad kopieert de bytes, zodat u de stream meteen kunt vrijgeven
procedure TFormMain.ApplyLayerChecklist(
const Changes: TPdfOptionalContentStateChanges);
var
Report: TPdfOptionalContentConfigureReport;
Output: TMemoryStream;
KeepPage: Integer;
begin
Output := TMemoryStream.Create;
try
try
if not Pdf.SaveAsOptionalContentConfigured(Output, Changes,
TPdfOptionalContentConfigureOptions.Default, Report) then
begin
ShowMessage('Layer update rejected: ' + Report.ErrorMessage);
Exit;
end;
except
on E: EPdfError do
begin
ShowMessage(E.Message); // ondertekend bestand: Output is nog leeg
Exit;
end;
end;
KeepPage := Pdf.PageNumber;
Pdf.Active := False;
Pdf.LoadDocument(Output); // Buffered staat standaard op True
Pdf.PageNumber := KeepPage;
finally
Output.Free;
end;
end;
Na de herlading schildert PDFium vanuit de nieuwe configuratie, dus het scherm en de volgende InspectOptionalContent-aanroep zijn het eens. De bekabeling tussen TPdf en TPdfView staat in een veelzijdige PDF-viewer bouwen met PDFium VCL in Delphi. Heeft een document nog geen lagen en moet u ze toevoegen, dan loopt PDF-lagen maken met OCG en OCMD in Delphi door SaveAsOptionalContentCreated heen
Prijzen, de trialbuild en de andere documentfuncties van de component staan vermeld op de productpagina van PDFium Component voor Delphi