PDFium Component controla las capas de contenido opcional de PDF (OCG) en Delphi mediante dos métodos de TPdf: InspectOptionalContent lista cada capa junto con la visibilidad que PDFium realmente renderizará, y SaveAsOptionalContentConfigured escribe una copia verificada en la que las capas que usted elija quedan encendidas o apagadas. El segundo método además neutraliza las reglas Usage y /AS que de otro modo desharían su edición en silencio. Ambos trabajan sobre el documento ya abierto en TPdf, así que no hay un segundo parser que mantener sincronizado con lo que el visor muestra
El pedido suele llegar de un despacho de CAD o GIS: el juego de planos sale con cotas, anotaciones y un cajetín de título en capas separadas, y el cliente quiere una copia con las cotas ocultas antes de mandársela a un proveedor. PDFium renderiza el contenido opcional correctamente, pero su ABI pública no tiene función para enumerar OCG, elegir una configuración o voltear el estado de una capa. Así que baja al nivel de objeto, edita /OCProperties, guarda, recarga, y la capa sigue ahí. La razón es la lógica de visibilidad de PDFium, y vale la pena entenderla antes de tocar un solo byte
¿Por qué editar /ON y /OFF no cambia lo que PDFium renderiza?
Editar los arreglos /ON y /OFF del diccionario de configuración no basta, porque PDFium deja que un estado explícito dentro del propio diccionario /Usage del OCG gane sobre esos arreglos, y una regla de auto-estado /AS puede entonces superar a ambos. ISO 32000-1 §8.11.4 describe configuraciones y diccionarios de uso como mecanismos separados; el renderizador de PDFium los pliega en una sola decisión, y InspectOptionalContent la reproduce en este orden:
- Partir del
/BaseStatede la configuración, donde/ONy/Unchangedcuentan ambos como visible y solo/OFFoculta - Aplicar el arreglo
/ONde la configuración, luego su arreglo/OFF, de modo que un grupo listado en ambos termine oculto - Aplicar el estado Usage explícito del grupo para el uso pedido, como
/Usage << /View << /ViewState /OFF >> >>, que supera todo lo anterior - Tratar como visible un grupo cuyo
/Intentno contiene ni/Viewni/All, ya que no participa en la visibilidad de intent de vista - Finalmente correr el arreglo
/ASde la configuración seleccionada, cuyas entradas para el evento coincidente fijan el estado de los grupos que listan
El tercer paso es el que quema a la gente. Un archivo guardado por una herramienta de maquetación suele cargar /ViewState /ON en cada OCG, y PDFium entonces ignora su arreglo /OFF editado con tanto cuidado: el guardado tiene éxito, el archivo reabre limpio, y la capa sigue pintándose. Para Print y Export, OcExplicitUsageState lee primero PrintState o ExportState y cae de vuelta a ViewState cuando la entrada específica no está, así que un ViewState /ON solitario clava la capa también para impresión. El marked content que referencia un OCMD (§8.11.2.2) se resuelve luego contra estos resultados por grupo, mediante la política /P o, cuando existe, la expresión de visibilidad /VE
¿Cómo lista las capas que PDFium realmente mostrará?
TPdf.InspectOptionalContent devuelve un TPdfOptionalContentInventory cuyo arreglo Groups lleva el número de objeto de cada OCG, el nombre, los intents, los tres estados Usage, el idioma, el rango de zoom, la bandera Locked, el índice de radio-group y el EffectiveVisible computado. El método primero hace que PDFium guarde el documento en memoria, expande los object streams, y escanea el resultado, así que las ediciones hechas antes en la sesión quedan reflejadas. El índice de configuración 0 es siempre el diccionario /D por defecto y las entradas de /Configs siguen desde el índice 1; el argumento por defecto de -1 selecciona el índice 0. Un documento sin /OCProperties hace que el método devuelva False con la razón en ErrorMessage en lugar de lanzar
procedure TFormMain.ListLayers;
var
Inv: TPdfOptionalContentInventory;
G: TPdfOptionalContentGroup;
begin
// Usage viene por defecto en ocuView; -1 selecciona la configuración 0, el diccionario /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;
El arreglo Memberships reporta cada OCMD con su Policy (ocmpAnyOn, ocmpAllOn, ocmpAnyOff, ocmpAllOff), el texto crudo de VisibilityExpression y su propio EffectiveVisible. Algunas reglas de borde son deliberadas. /P viene por defecto en /AnyOn, y un OCMD sin grupos cuenta como visible. Una referencia a un número de objeto que no es un OCG conocido se trata como visible en lugar de fallar toda la expresión. La evaluación de /VE se detiene a una profundidad de anidamiento de 32 y trata cualquier cosa más profunda como oculta, lo que evita que una expresión hostil o autorreferente convierta la inspección en un stack overflow
Escribir un nuevo estado de capa con SaveAsOptionalContentConfigured
TPdf.SaveAsOptionalContentConfigured toma un arreglo de records TPdfOptionalContentStateChange (número de objeto del grupo más Visible) y escribe un documento en el que la configuración seleccionada produce exactamente ese estado. La configuración seleccionada recibe /BaseState /ON más arreglos /ON y /OFF completos cubriendo cada grupo, y cada OCG que ya tiene un diccionario Usage recibe un ViewState explícito (o PrintState / ExportState, siguiendo Options.Usage) que corresponde a su nuevo estado. Con TPdfOptionalContentConfigureOptions.Default, la clave /AS de la configuración seleccionada se elimina para que un evento de apertura, impresión o exportación no pueda voltear las capas de vuelta
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;
// Configuración 0, ocuView, DisableAutomaticState y EnforceRadioGroups en 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;
El camino de escritura conserva la salida guardada del propio PDFium como prefijo byte a byte y agrega solo el dueño de configuración reescrito y los objetos OCG que cargan diccionarios Usage, seguidos de una nueva sección xref y el trailer. Antes de que un solo byte llegue a su destino, el resultado se reabre en un TPdf separado bajo la política de carga estricta, y el método falla si la tabla de referencias cruzadas no valida. La sobrecarga de archivo va un paso más allá: escribe a un archivo temporal junto al destino y reemplaza el destino solo después de que la verificación tiene éxito, así que una actualización rechazada jamás deja un plano a medio escribir. Es el mismo enfoque de revisión incremental verificada que usa el editor de name trees y number trees de PDF en PDFium Component
¿A qué se niega el guardado configurado?
El guardado configurado se niega a cualquier cambio que el propio documento prohíba o no pueda representar de forma segura, y cada negación ocurre antes de tocar el destino. Un número de objeto que no está en /OCGs falla sin más. Cambiar un grupo listado en el arreglo /Locked de la configuración falla, aunque reafirmar su valor actual está permitido. Con EnforceRadioGroups activado, cualquier conjunto /RBGroups que termine con más de un miembro visible se rechaza en lugar de apagar a los demás en silencio. Los documentos cifrados se rechazan porque los objetos incrementales en plaintext no pueden cargar el security handler activo. Los documentos firmados lanzan EPdfError salvo que usted pase AllowSignedDocument = True, ya que cambiar lo que una página muestra puede romper la cobertura de firma o una política de certificación
function TFormMain.SavePrintPreset(Target: TStream;
const Changes: TPdfOptionalContentStateChanges): Boolean;
var
Options: TPdfOptionalContentConfigureOptions;
Report: TPdfOptionalContentConfigureReport;
begin
Options := TPdfOptionalContentConfigureOptions.Default;
Options.Usage := ocuPrint; // escribe /Print << /PrintState ... >>
Options.ConfigurationIndex := 1; // primera entrada de /Configs, no /D
try
Result := Pdf.SaveAsOptionalContentConfigured(Target, Changes, Options,
Report); // AllowSignedDocument sigue en False
if not Result then
ShowMessage(Report.ErrorMessage);
except
on E: EPdfError do
begin
ShowMessage(E.Message); // archivo firmado: nada escrito al Target
Result := False;
end;
end;
end;
Conozca los trade-offs antes de conectar esto a un trabajo por lotes. La revisión agregada se monta sobre el re-guardado completo de PDFium, no sobre los bytes originales de su archivo, que es exactamente por qué la entrada firmada necesita consentimiento explícito. La reescritura además normaliza la configuración seleccionada a /BaseState /ON, así que una línea base /Unchanged o /OFF del autor queda reemplazada por arreglos explícitos con la misma visibilidad resultante. Soltar /AS elimina trucos solo de impresión como una capa de marca de agua que aparece únicamente en papel; ponga DisableAutomaticState en False para conservar esas reglas, aceptando que pueden superar el estado que usted pidió para ese evento. Del lado positivo, PDF/A-2 (ISO 19005-2 cláusula 6.9) y PDF/UA (ISO 14289-1 cláusula 7.10) prohíben ambos /AS en los diccionarios de configuración, así que la salida por defecto elimina un issue que su validación preflight PDF/A con PDFium Component de otro modo reportaría
Dónde encaja el control de capas en un visor PDF de Delphi
En un visor, el control de capas es una checklist alimentada por el inventario más una recarga del resultado guardado. Llene la checklist desde Groups, deshabilite las entradas que estén Locked, trate como mutuamente excluyentes a los miembros que comparten un RadioGroupIndex, y al aplicar escriba a un TMemoryStream y cargue ese stream de vuelta en TPdf para que la vista pinte el nuevo estado. El cableado entre TPdf y TPdfView está cubierto en cómo construir un visor PDF completo con PDFium VCL en Delphi. Licencias, descargas de prueba y el resto del set de funcionalidades están en la página de producto de PDFium Component para Delphi