PDFium Component controlla i livelli optional content PDF (OCG) in Delphi tramite due metodi di TPdf: InspectOptionalContent elenca ogni livello insieme alla visibilità che PDFium renderizzerà davvero, e SaveAsOptionalContentConfigured scrive una copia verificata in cui i livelli che scegliete restano accesi o spenti. Il secondo metodo neutralizza anche le regole Usage e /AS che altrimenti annullerebbero in silenzio la vostra modifica. Entrambi lavorano sul documento già aperto in TPdf, quindi non c'è un secondo parser da tenere allineato con ciò che il viewer mostra
La richiesta di solito arriva da uno studio CAD o GIS: il corredo di tavole spedisce quote, annotazioni e cartiglio su livelli separati, e il cliente vuole una copia con le quote nascoste prima che vada al fornitore. PDFium renderizza l'optional content correttamente, ma il suo ABI pubblico non ha alcuna funzione per enumerare gli OCG, scegliere una configurazione o ribaltare lo stato di un livello. Così scendete al livello degli oggetti, modificate /OCProperties, salvate, ricaricate, e il livello è ancora lì. La ragione è la logica di visibilità di PDFium, e vale la pena capirla prima di toccare qualsiasi byte
Perché modificare /ON e /OFF non cambia ciò che PDFium renderizza?
Modificare gli array /ON e /OFF del dizionario di configurazione non basta, perché PDFium lascia che uno stato esplicito dentro il dizionario /Usage dello stesso OCG vinca su quegli array, e una regola auto-state /AS può poi scavalcare entrambi. ISO 32000-1 §8.11.4 descrive configurazioni e dizionari usage come meccanismi separati; il renderer di PDFium li fonde in un'unica decisione, e InspectOptionalContent la riproduce in quest'ordine:
- Partite dal
/BaseStatedella configurazione, dove/ONe/Unchangedcontano entrambi come visibili e solo/OFFnasconde - Applicate l'array
/ONdella configurazione, poi il suo/OFF, così un gruppo elencato in entrambi finisce nascosto - Applicate lo stato Usage esplicito del gruppo per l'usage richiesto, come
/Usage << /View << /ViewState /OFF >> >>, che scavalca tutto ciò che sta sopra - Trattate come visibile un gruppo il cui
/Intentnon contiene né/Viewné/All, visto che non prende parte alla visibilità per intent di vista - Infine eseguite l'array
/ASdella configurazione selezionata, le cui voci per l'evento corrispondente impostano lo stato dei gruppi che elencano
Il terzo passo è quello che brucia la gente. Un file salvato da uno strumento di impaginazione spesso porta /ViewState /ON su ogni OCG, e PDFium allora ignora il vostro array /OFF editato con cura: il salvataggio riesce, il file si riapre pulito, e il livello continua a dipingersi. Per Print ed Export, OcExplicitUsageState legge prima PrintState o ExportState e ricade su ViewState quando la voce specifica è assente, così un solitario ViewState /ON inchioda il livello anche per la stampa. Il marked content che referenzia un OCMD (§8.11.2.2) viene poi risolto contro questi risultati per gruppo, tramite la policy /P o, quando presente, l'espressione di visibilità /VE
Come si elencano i livelli che PDFium mostrerà davvero?
TPdf.InspectOptionalContent restituisce un TPdfOptionalContentInventory il cui array Groups porta per ogni OCG il numero di oggetto, il nome, gli intent, i tre stati Usage, la lingua, il range di zoom, il flag Locked, l'indice del radio group e l'EffectiveVisible calcolato. Il metodo prima fa salvare a PDFium il documento corrente in memoria, espande gli object stream, e scandisce il risultato, così le modifiche fatte prima nella sessione si riflettono. L'indice di configurazione 0 è sempre il dizionario predefinito /D e le voci di /Configs seguono dall'indice 1; l'argomento predefinito -1 seleziona l'indice 0. Un documento senza /OCProperties fa restituire al metodo False con la ragione in ErrorMessage invece di sollevare un'eccezione
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Usage vale ocuView per default; -1 seleziona la configurazione 0, il dizionario /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;
L'array Memberships riporta ogni OCMD con la sua Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), il testo grezzo della VisibilityExpression e il suo EffectiveVisible. Qualche regola di bordo è deliberata. /P vale /AnyOn per default, e un OCMD senza gruppi conta come visibile. Un riferimento a un numero di oggetto che non è un OCG noto è trattato come visibile invece di far fallire l'intera espressione. La valutazione di /VE si ferma a una profondità di annidamento di 32 e tratta tutto ciò che sta più in profondità come nascosto, il che impedisce a un'espressione ostile o auto-referente di trasformare l'ispezione in uno stack overflow
Scrivere un nuovo stato dei livelli con SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured prende un array di record TPdfOptionalContentStateChange (numero di oggetto del gruppo più Visible) e scrive un documento in cui la configurazione selezionata produce esattamente quello stato. La configurazione selezionata riceve /BaseState /ON più array /ON e /OFF completi che coprono ogni gruppo, e ogni OCG che ha già un dizionario Usage riceve un ViewState esplicito (o PrintState / ExportState, secondo Options.Usage) corrispondente al suo nuovo stato. Con TPdfOptionalContentConfigureOptions.Default, la chiave /AS della configurazione selezionata viene rimossa così che un evento di open, print o export non possa ribaltare i livelli
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;
// Configurazione 0, ocuView, DisableAutomaticState e 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;
Il percorso di scrittura conserva l'output salvato da PDFium stesso come prefisso byte per byte e accoda solo il proprietario di configurazione riscritto e gli oggetti OCG che portano dizionari Usage, seguiti da una nuova sezione xref e da un trailer. Prima che un solo byte raggiunga la destinazione, il risultato viene riaperto in un TPdf separato sotto la policy di caricamento severa, e il metodo fallisce se la tabella dei riferimenti incrociati non valida. L'overload su file va un passo oltre: scrive su un file temporaneo accanto al target e sostituisce il target solo dopo che la verifica è riuscita, così un aggiornamento respinto non lascia mai dietro una tavola scritta a metà. È lo stesso approccio di revisione incrementale verificata usato dall'editor di name tree e number tree PDF in PDFium Component
Che cosa si rifiuta di fare il salvataggio configurato?
Il salvataggio configurato si rifiuta di applicare qualsiasi modifica che il documento stesso vieta o non può rappresentare in sicurezza, e ogni rifiuto avviene prima che la destinazione venga toccata. Un numero di oggetto che non sta in /OCGs fallisce a prescindere. Cambiare un gruppo elencato nell'array /Locked della configurazione fallisce, benché ribadire il suo valore corrente sia permesso. Con EnforceRadioGroups attivo, qualsiasi insieme /RBGroups che finirebbe con più di un membro visibile viene respinto invece di spegnere gli altri in silenzio. I documenti cifrati vengono respinti perché gli oggetti incrementali in chiaro non possono trasportare l'handler di sicurezza attivo. I documenti firmati sollevano EPdfError a meno che non passiate AllowSignedDocument = True, visto che cambiare ciò che una pagina mostra può rompere la copertura della firma o una policy di certificazione
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // scrive /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // prima voce di /Configs, non /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // AllowSignedDocument resta False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // file firmato: nulla scritto su Target
Result := False;
end;
end;
end;
Conoscete i trade-off prima di incapsulare tutto ciò in un job batch. La revisione accodata sta sopra il re-save completo di PDFium, non sopra i byte del vostro file originale, ed è esattamente per questo che l'input firmato richiede un consenso esplicito. La riscrittura normalizza anche la configurazione selezionata a /BaseState /ON, quindi una baseline /Unchanged o /OFF dell'autore viene sostituita da array espliciti con la stessa visibilità risultante. Togliere /AS elimina i trucchetti solo-stampa come un livello watermark che compare solo su carta; impostate DisableAutomaticState a False per conservare quelle regole, accettando che possano scavalcare lo stato richiesto per quell'evento. Sul versante positivo, PDF/A-2 (ISO 19005-2 clausola 6.9) e PDF/UA (ISO 14289-1 clausola 7.10) vietano entrambi /AS nei dizionari di configurazione, quindi l'output predefinito toglie un problema che la vostra validazione preflight PDF/A con PDFium Component altrimenti segnalerebbe
Dove sta il controllo dei livelli in un viewer PDF Delphi
In un viewer, il controllo dei livelli è una checklist alimentata dall'inventario più un ricaricamento del risultato salvato. Riempite la checklist da Groups, disabilitate le voci Locked, trattate i membri che condividono uno stesso RadioGroupIndex come mutuamente esclusivi, e all'applicazione scrivete su un TMemoryStream e ricaricate quello stream in TPdf così la vista dipinge il nuovo stato. Il cablaggio tra TPdf e TPdfView è trattato in costruire un viewer PDF ricco di funzionalità con PDFium VCL in Delphi. Licenze, download di prova e il resto del set di funzionalità sono sulla pagina del prodotto PDFium Component per Delphi