Artículo técnico

Redacción real de PDF en Delphi: eliminación y desinfección

La redacción real de PDF en Delphi significa eliminar los bytes subyacentes, no pintar sobre ellos. losLab PDF Library dibuja esa línea con dos API: RedactRegion edita el flujo de contenido de la página para que el texto, los vectores y las imágenes eliminadas desaparezcan físicamente, y SanitizeDocument limpia los metadatos, scripts y acciones donde también se oculta la información redactada. Si hace mal cualquiera de las dos partes, enviará un archivo que parece censurado en pantalla pero que devuelve el secreto a cualquiera que copie el texto o abra las Propiedades del documento

El modo de fallo es de conocimiento público. Periódicos, tribunales y agencias gubernamentales han publicado archivos PDF con un rectángulo negro superpuesto sobre un nombre o un número, solo para que un lector seleccione el texto debajo, lo copie y pegue la cadena supuestamente redactada a la vista de todos. El rectángulo era un dibujo. El texto seguía allí, en el flujo de contenido, una capa por debajo de la pintura, completamente intacto y seleccionable. losLab PDF Library, el motor PDF para Delphi y C++Builder expuesto a través de la clase TPDFlib, trata esa distinción como el objetivo principal de la redacción en lugar de una idea tardía

¿Por qué dibujar un cuadro negro sobre el texto de un PDF no es una redacción real?

losLab PDF Library expone MaskRect para los casos en los que cubrir es realmente lo que desea: una marca de agua, un oscurecimiento visual, un sello de prueba. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) añade un rectángulo al flujo de contenido de la página y lo pinta con el operador de construcción de ruta re seguido del operador de relleno f (ISO 32000-1 §8.5). Cada byte que ya estaba en la página —los glifos del nombre que cubrió, los trazos vectoriales, la imagen incrustada— permanece exactamente donde estaba, una instrucción antes en el mismo flujo de datos. Un visor compatible dibuja el cuadro negro al final, por lo que prevalece visualmente. La cubierta no cambia nada debajo

Cualquiera puede recuperar el contenido original de tres maneras: seleccionar y copiar el texto directamente a través del cuadro opaco, ejecutar un extractor de texto en la página o eliminar el rectángulo de cobertura del flujo de contenido. MaskRect es honesto al respecto en su propio nombre: enmascara (masks), no elimina. El resultado es una pegatina sobre una palabra que aún puede leer levantando la pegatina

Cómo RedactRegion elimina contenido a nivel de instrucción

RedactRegion(Page, Left, Top, Width, Height, Options) funciona una capa por debajo de la pintura. losLab PDF Library analiza cada capa de contenido de la página en un TPDFContentProgram, recorre la lista de instrucciones con un analizador de seguimiento CTM (FindInstructionsInRect en el modelo de contenido) y marca cada operador de visualización de texto, pintura de ruta y operador de imagen Do cuya área pintada intersecte su rectángulo. Esas instrucciones se eliminan con TPDFContentProgram.Delete, y la capa editada se escribe de nuevo con WritePageContentLayer. Los bytes no se deslizan detrás de una cubierta: salen del flujo de datos. Un extractor de texto ejecutado posteriormente no encuentra nada, porque no hay nada que encontrar

Vale la pena tener en cuenta dos detalles de implementación. RedactRegion toma un rectángulo superior izquierdo en el origen y unidades actuales y lo convierte internamente al espacio de usuario inferior izquierdo que utiliza el flujo de contenido, por lo que usted especifica la región tal como la ve en la página. Cuando un rectángulo cubre varias instrucciones, RedactRegion las elimina en orden descendente de índice, porque eliminar la instrucción 4 antes de la instrucción 7 desplazaría cada índice posterior y corrompería el rango. Si pasa Options = 0, losLab PDF Library también estampa un cuadro negro sólido sobre la región limpia como marcador visible, pero ese marcador es cosmético, aplicado después de que el contenido real ya se ha eliminado

En la práctica, rara vez se codifica el rectángulo de forma fija. Se busca en la página la cadena de texto sensible y se lee su cuadro delimitador —la misma búsqueda de página y enumeración de elementos cubiertas en localización de texto y elementos de página en Delphi— y luego se entrega ese rectángulo a RedactRegion:

var
  PDF: TPDFlib;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('contract.pdf', '') = 1 then
    begin
      // Delete everything painted inside a 220 x 14 pt box on page 1.
      // Coordinates are top-left, in the current origin and units.
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);   // Options=0 stamps a black marker
      PDF.SaveToFile('contract-redacted.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

¿Dónde se sigue ocultando la información redactada en un PDF?

La redacción que se detiene en la capa visible deja un rastro, porque un PDF almacena información en lugares que un lector nunca mira. losLab PDF Library agrupa las fugas en canales que vale la pena mencionar. El Diccionario de información del documento y el flujo de metadatos XMP suelen contener cadenas de texto de Autor, Productor o Palabras clave que nombran a la misma persona o caso que eliminó del cuerpo. El JavaScript a nivel de documento y la acción OpenAction del catálogo pueden ejecutarse al abrir el archivo y acceder a datos que creía que habían desaparecido. Cada anotación lleva una acción /A opcional, y una anotación en sí misma —una nota adhesiva, un campo de formulario antiguo, un enlace— puede ubicarse directamente sobre el punto redactado con su propia copia del texto. Nada de esto se ve afectado por el cifrado; un archivo cifrado con un paquete XMP de texto plano sigue filtrando su título a cualquier indexador, una brecha que vale la pena auditar el cifrado y los permisos por sí misma. La desinfección defendible tiene que visitar cada uno de estos lugares ocultos, no solo la página que estaba mirando

Limpieza del documento con SanitizeDocument

losLab PDF Library responde a esa propagación de fugas con SanitizeDocument(Flags), una llamada de orquestación que ejecuta un conjunto de limpiezas independientes seleccionadas mediante una máscara de bits y devuelve el número de limpiezas que aplicó. Las banderas son combinable. SANITIZE_JAVASCRIPT elimina cada paquete de JavaScript a nivel de documento y cualquier script OpenAction. SANITIZE_ACTIONS elimina el diccionario de acciones /A de cada anotación, a través del nuevo TPDFAnnots.RemoveAnnotAction, y limpia la acción OpenAction del catálogo. SANITIZE_METADATA borra Autor, Asunto, Palabras clave, Creador y Productor tanto en el Diccionario de información del documento (ISO 32000-1 §14.3.3) como en el flujo de metadatos XMP (§14.3.2). SANITIZE_ANNOTATIONS elimina cada anotación en todo el documento, y SANITIZE_OPEN_ACTION elimina la entrada /OpenAction del catálogo. SANITIZE_ALL es la unión de las cinco a nivel bit a bit

Las convenciones de índice dentro de estas operaciones de limpieza son exactamente el tipo de cosas que rompen silenciosamente un bucle personalizado, razón por la cual SanitizeDocument existe como una sola llamada. GlobalJavaScriptPackageName se basa en 0, y RemoveGlobalJavaScript reduce la lista a medida que avanza, por lo que la limpieza de JavaScript siempre lee el índice de paquete 0 y lo elimina hasta que el recuento llega a cero. DeleteAnnotation, por el contrario, se basa en 1. AnnotationCount informa sobre la página seleccionada actualmente, por lo que las operaciones de anotación seleccionan cada página a su vez antes de contar. losLab PDF Library maneja estos límites correctamente a nivel interno, por lo que usted pasa un conjunto de banderas y lee un recuento de vuelta en lugar de reproducir cinco reglas de iteración diferentes

var
  Applied: Integer;
begin
  // Strip metadata, scripts, and actions but keep the annotations:
  Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
    SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);

  // Or clear every hidden channel, annotations included:
  Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;

El flujo de trabajo completo de redacción y desinfección

Al combinar ambos, el procedimiento completo es corto. losLab PDF Library elimina el contenido visible con RedactRegion, página por página, y luego limpia cada canal oculto con SanitizeDocument(SANITIZE_ALL) antes de escribir el archivo. Debido a que la redacción casi siempre se ejecuta en documentos que llegaron fuera de su control, vale la pena cargarlos a través de una ruta protegida —la misma postura defensiva descrita en análisis seguro de archivos PDF no confiables— para que una entrada malformada falle de manera limpia en lugar de a mitad del paso de redacción

var
  PDF: TPDFlib;
  Applied: Integer;
begin
  PDF := TPDFlib.Create;
  try
    if PDF.LoadFromFile('incoming.pdf', '') = 1 then
    begin
      PDF.RedactRegion(1, 90, 705, 220, 14, 0);       // delete the sensitive line
      Applied := PDF.SanitizeDocument(SANITIZE_ALL);   // clear metadata, JS, actions, annots
      PDF.SaveToFile('published.pdf');
    end;
  finally
    PDF.Free;
  end;
end;

Dónde se detiene la redacción del flujo de contenido

RedactRegion opera en el flujo de contenido, que es exactamente donde vive el texto nacido digitalmente y exactamente donde no vive el texto escaneado. Cuando una página es una imagen escaneada, las palabras son píxeles dentro de un objeto XObject de imagen, no operadores de texto, y losLab PDF Library no puede recortar un nombre del medio de un rasterizador de la forma en que elimina un glifo. Su manejo de imágenes es deliberadamente conservador: cualquier imagen cuya ubicación intersecte el rectángulo se elimina por completo, por lo que un cuadro de redacción en una página escaneada se lleva todo el escaneo en lugar de una sección de este. Redactar una región dentro de una imagen significa rasterizar, pintar y volver a incrustar a nivel de píxel, lo cual es una operación diferente a la eliminación del flujo de contenido. La misma precaución se aplica a una capa de texto OCR sobre una página escaneada: RedactRegion elimina los operadores de texto que puede ver, pero la imagen debajo permanece intacta a menos que su objeto XObject de imagen también intersecte el cuadro

Dos puntos más detallados mantienen la honestidad de los resultados. La prueba de intersección aproxima el ancho de cada segmento de texto a partir de su recuento de caracteres y tamaño de fuente en lugar de medir las métricas exactas del glifo, por lo que una palabra muy corta establecida en una fuente proporcional puede escaparse de un rectángulo dibujado de manera muy ajustada; dele al cuadro un pequeño margen. La eliminación también es granular a nivel de instrucción: RedactRegion elimina operadores de visualización de texto completos, por lo que un rectángulo que recorta solo parte de un segmento puede llevarse caracteres adyacentes que compartieron el mismo operador con él. Ninguno de los límites es una razón para desconfiar del mecanismo, solo una razón para delimitar el rectángulo con cuidado y verificar el resultado

La disciplina que cierra todo flujo de trabajo de redacción es la verificación, no la confianza. Redacte la región, desinfecte el documento, guárdelo en un archivo nuevo, vuelva a abrirlo e intente recuperar tanto el texto que eliminó como los metadatos que limpió; cuando ambos regresen vacíos, la redacción se mantuvo. Las API RedactRegion y SanitizeDocument que se muestran aquí se incluyen con losLab PDF Library para Delphi y C++Builder, junto con el modelo de contenido completo y la referencia de desinfección