Teknisk artikkel

Slå PDF optional content-lag på og av i Delphi med PDFium

PDFium Component styrer PDF optional content-lag (OCG-er) i Delphi gjennom to TPdf-metoder: InspectOptionalContent lister opp hvert lag sammen med synligheten PDFium faktisk vil rendre, og SaveAsOptionalContentConfigured skriver en verifisert kopi der lagene du velger, er slått på eller av. Den andre metoden nøytraliserer også Usage- og /AS-reglene som ellers stille ville omgjort redigeringen din. Begge jobber på dokumentet som allerede er åpent i TPdf, så det finnes ingen andre parser å holde i sync med det viseren viser

Forespørselen kommer vanligvis fra et CAD- eller GIS-hus: tegningsettet leveres med mål, annotasjoner og en tittelblokk på separate lag, og kunden vil ha en kopi med målene skjult før den går til en leverandør. PDFium renderer optional content riktig, men det offentlige ABI-et har ingen funksjon til å enumerate OCG-er, velge en konfigurasjon eller vippe en lagtilstand. Så du går ned på objektnivå, redigerer /OCProperties, lagrer, laster på nytt, og laget er der fortsatt. Årsaken er PDFiums synlighetslogikk, og den er verdt å forstå før du rører noen byte

Hvorfor endrer redigering av /ON og /OFF ikke det PDFium renderer?

Å redigere /ON- og /OFF-matrisene i konfigurasjonsordboken er ikke nok, fordi PDFium lar en eksplisitt tilstand inne i OCG-ens egen /Usage-ordbok vinne over de matrisene, og en /AS-auto-tilstandsregel kan deretter overstyre begge. ISO 32000-1 §8.11.4 beskriver konfigurasjoner og usage-ordbøker som adskilte mekanismer; PDFiums renderer folder dem sammen til én enkelt beslutning, og InspectOptionalContent gjenskaper den i denne rekkefølgen:

  • Start fra konfigurasjonens /BaseState, der /ON og /Unchanged begge teller som synlige og bare /OFF skjuler
  • Anvend konfigurasjonens /ON-matrise, deretter /OFF-matrisen, så en gruppe oppført i begge ender skjult
  • Anvend gruppens eksplisitte Usage-tilstand for den etterspurte bruken, som /Usage << /View << /ViewState /OFF >> >>, som overstyrer alt over
  • Behandle en gruppe hvis /Intent inneholder verken /View eller /All som synlig, siden den ikke deltar i view-intent-synlighet
  • Til slutt kjøre /AS-matrisen til den valgte konfigurasjonen, hvis oppføringer for den matchende hendelsen setter tilstanden til gruppene de lister
Den femtrinns synlighetsbeslutningen PDFium Component spiller av for hver PDF optional content-gruppe i Delphi: BaseState setter starten, konfigurasjonens ON- og OFF-matriser anvendes i rekkefølge, en eksplisitt Usage ViewState- eller PrintState-oppføring overstyrer begge, Intent ikke-deltakelse teller som synlig, og AS-matrisen kjører til slutt
Å redigere ON- og OFF-matrisene er ikke nok, fordi PDFium folder BaseState, begge matriser, gruppe-Usage-tilstanden og til slutt AS-auto-tilstandsreglene inn i én kjennelse som InspectOptionalContent gjenskaper trinn for trinn

Det tredje steget er det som brenner folk. En fil lagret av et layoutverktøy bærer ofte /ViewState /ON på hver OCG, og PDFium ignorerer da din omhyggelig redigerte /OFF-matrise: lagringen lykkes, filen åpnes rent igjen, og laget tegnes fortsatt. For Print og Export leser OcExplicitUsageState PrintState eller ExportState først og faller tilbake til ViewState når den spesifikke oppføringen mangler, så en enslig ViewState /ON fester laget for utskrift også. Marked content som refererer til en OCMD (§8.11.2.2) løses deretter mot disse per-gruppe-resultatene, gjennom /P-policyen eller, når den finnes, /VE-synlighetsuttrykket

Hvordan lister du opp lagene PDFium faktisk viser?

TPdf.InspectOptionalContent returnerer en TPdfOptionalContentInventory hvis Groups-matrise bærer hver OCGs objektnummer, navn, intents, de tre Usage-tilstandene, språk, zoom-område, Locked-flagg, radiogruppe-indeks og den beregnede EffectiveVisible. Metoden får først PDFium til å lagre det gjeldende in-memory-dokumentet, ekspanderer objektstrømmer og skanner resultatet, så redigeringer gjort tidligere i økten gjenspeiles. Konfigurasjonsindeks 0 er alltid standard /D-ordboken, og oppføringene i /Configs følger fra indeks 1; standardargumentet -1 velger indeks 0. Et dokument uten /OCProperties får metoden til å returnere False med årsaken i ErrorMessage i stedet for å kaste unntak

procedure TFormMain.ListLayers;
var
  Inv: TPdfOptionalContentInventory;
  G: TPdfOptionalContentGroup;
begin
  // Usage står som standard til ocuView; -1 velger konfigurasjon 0, /D-ordboken
  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-matrisen rapporterer hver OCMD med Policy sin (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), rå VisibilityExpression-tekst og sin egen EffectiveVisible. Noen kantregler er bevisste. /P står som standard til /AnyOn, og en OCMD uten grupper teller som synlig. En referanse til et objektnummer som ikke er en kjent OCG, behandles som synlig i stedet for å feile hele uttrykket. /VE-evaluering stopper på en hekkedybde på 32 og behandler alt dypere som skjult, noe som hindrer et fiendtlig eller selvrefererende uttrykk i å gjøre inspeksjonen om til en stack overflow

Skrive en ny lagtilstand med SaveAsOptionalContentConfigured

TPdf.SaveAsOptionalContentConfigured tar imot en matrise av TPdfOptionalContentStateChange-records (gruppeobjektnummer pluss Visible) og skriver et dokument der den valgte konfigurasjonen produserer nøyaktig den tilstanden. Den valgte konfigurasjonen får /BaseState /ON pluss komplette /ON- og /OFF-matriser som dekker hver gruppe, og hver OCG som allerede har en Usage-ordbok, får en eksplisitt ViewState (eller PrintState / ExportState, etter Options.Usage) som matcher den nye tilstanden sin. Med TPdfOptionalContentConfigureOptions.Default fjernes /AS-nøkkelen til den valgte konfigurasjonen, slik at en open-, print- eller export-hendelse ikke kan vippe lagene tilbake

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;

  // Konfigurasjon 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 lagrede output som en byte-for-byte-prefiks og legger bare til den omskrevne konfigurasjonseieren og OCG-objektene som bærer Usage-ordbøker, etterfulgt av en ny xref-seksjon og trailer. Før én eneste byte når målet ditt, åpnes resultatet på nytt i en separat TPdf under den strenge lastepolicyen, og metoden feiler hvis kryssreferansetabellen ikke validerer. Fil-overloaden går et steg lenger: den skriver til en midlertidig fil ved siden av målet og erstatter målet først etter at verifiseringen lykkes, så en avvist oppdatering etterlater aldri en halvskrevet tegning. Det er samme verifiserte inkrementelle revisjonstilnærming som brukes av PDF navnetre- og nummertre-redigereren i PDFium Component

Hvordan SaveAsOptionalContentConfigured i PDFium Component skriver en Delphi-PDF med lag slått av og på: tilstandsendringer og valg går inn, den valgte konfigurasjonen skrives om med komplette ON- og OFF-matriser og Usage-tilstander, den verifiserte inkrementelle revisjonen legges til, og en streng gjenåpning må validere før noe skrives
Den konfigurerte lagringen beholder PDFiums egen reagre-lagring som byte-prefiks, legger til den omskrevne konfigurasjonseieren pluss en ny xref-seksjon, og åpner resultatet på nytt i en separat TPdf før målet røres

Hva nekter den konfigurerte lagringen å gjøre?

Den konfigurerte lagringen nekter enhver endring dokumentet selv forbyr eller ikke kan representere trygt, og hvert avslag skjer før målet røres. Et objektnummer som ikke er i /OCGs, feiler kontant. Å endre en gruppe oppført i konfigurasjonens /Locked-matrise feiler, selv om å gjenopprette den gjeldende verdien er tillatt. Med EnforceRadioGroups på avvises ethvert /RBGroups-sett som ville ende med mer enn ett synlig medlem, i stedet for å slå de andre av stille. Krypterte dokumenter avvises fordi klartekst-inkrementobjekter ikke kan bære den aktive sikkerhetshåndtereren. Signerte dokumenter kaster EPdfError med mindre du sender AllowSignedDocument = True, siden å endre det en side viser, kan bryte signaturdekning eller en sertifiseringspolicy

Avslagsportene SaveAsOptionalContentConfigured anvender i PDFium Component før en konfigurert Delphi-PDF skrives: et objektnummer utenfor OCG-er feiler, låste grupper feiler, RBGroups-sett med mer enn ett synlig medlem avvises, krypterte dokumenter kan ikke bære klartekst-inkrementobjekter, og signerte filer krever AllowSignedDocument
Hvert avslag skjer før målet røres, og feilårsaken havner i Report.ErrorMessage i stedet for å etterlate en halvskrevet tegning
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 oppføring i /Configs, ikke /D
  try
    Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
      Report);                        // AllowSignedDocument forblir False
    if not Result then
      ShowMessage(Report.ErrorMessage);
  except
    on E: EPdfError do
    begin
      ShowMessage(E.Message);         // signert fil: ingenting skrevet til Target
      Result := False;
    end;
  end;
end;

Kjenn avveiningene før du kobler dette inn i en batchjobb. Den tillagte revisjonen sitter oppå PDFiums fulle reagre, ikke de opprinnelige filbytene dine, og det er nøyaktig derfor signert input trenger eksplisitt samtykke. Omskrivingen normaliserer også den valgte konfigurasjonen til /BaseState /ON, så en forfatters /Unchanged- eller /OFF-basislinje erstattes av eksplisitte matriser med samme resulterende synlighet. Å droppe /AS fjerner utskriftsorgan-triks som et vannmerkelag som bare vises på papir; sett DisableAutomaticState til False for å beholde de reglene, og godta at de kan overstyre den etterspurte tilstanden din for den hendelsen. På plussiden forbyr både PDF/A-2 (ISO 19005-2 klausul 6.9) og PDF/UA (ISO 14289-1 klausul 7.10) /AS i konfigurasjonsordbøker, så standard-outputen fjerner ett problem PDF/A-preflight-validering med PDFium Component ellers ville rapportert

Hvor lagstyring passer inn i en Delphi PDF-viser

I en viser er lagstyring en avkryssingsliste drevet av inventaret pluss en gjeninnlesing av det lagrede resultatet. Fyll avkryssingslisten fra Groups, deaktiver oppføringene som er Locked, behandle medlemmer som deler en RadioGroupIndex som gjensidig utelukkende, og ved bruk skriv til en TMemoryStream og last den strømmen tilbake inn i TPdf, så viseren tegner den nye tilstanden. Kablingen mellom TPdf og TPdfView er dekket i å bygge en funksjonsrik PDF-viser med PDFium VCL i Delphi. Lisensiering, prøvenedlastinger og resten av funksjonssettet finnes på produktsiden for PDFium Component for Delphi