Los archivos adjuntos de PDF se almacenan en el árbol de archivos incrustados del documento, una estructura que la mayoría de los visores presentan como un panel de clip de papel o una barra lateral de adjuntos. Desde el código de Delphi, el Componente PDFium expone ese árbol a través de un pequeño conjunto de propiedades indexadas en TPdf: usted itera mediante un índice entero, lee nombres y contenidos de bytes, crea nuevos espacios y elimina los existentes. La interfaz de la API es reducida; solo hay unas pocas restricciones de orden y una regla de desinfección (sanitization) que vale la pena conocer antes de escribir código de producción
Leer archivos adjuntos de un documento abierto
AttachmentCount proporciona la cantidad de archivos incrustados que declara el documento. Lee directamente del valor subyacente de PDFium, por lo que refleja únicamente lo que el PDF contiene en realidad. A partir de ahí, AttachmentName[Index] devuelve el nombre para mostrar como un tipo WString, y Attachment[Index] entrega los bytes brutos como una matriz TBytes. Ambos están basados en cero. El documento debe estar abierto (Pdf.Active = True) antes de consultar cualquiera de las propiedades; llamarlas en un documento cerrado devuelve cero o un resultado vacío sin generar excepciones
Un detalle a tener en cuenta: Attachment[Index] asigna memoria y devuelve el contenido completo del archivo en cada lectura. Para un documento que contenga un recurso incrustado de gran tamaño, iterar por todos los adjuntos para crear una lista de visualización implica pagar ese costo de asignación en cada llamada. Si solo necesita los nombres para mostrarlos en pantalla, lea primero AttachmentName y aplace la obtención de bytes hasta que el usuario solicite el archivo
procedure ListAttachments(Pdf: TPdf);
var
I: Integer;
Data: TBytes;
begin
if not Pdf.Active then
Exit;
for I := 0 to Pdf.AttachmentCount - 1 do
begin
Data := Pdf.Attachment[I];
Writeln(Format('%d: %s (%d bytes)',
[I, Pdf.AttachmentName[I], Length(Data)]));
end;
end;
Extraer un archivo adjunto al disco
No existe una función de ayuda SaveAttachment. Usted lee los bytes y los escribe donde los necesite, lo que deja la construcción de rutas y la desinfección enteramente a cargo de su código. Esto resulta importante cuando los nombres de los adjuntos provienen de documentos no confiables. Los nombres de los adjuntos en PDF son cadenas de texto almacenadas en el archivo; pueden contener separadores de ruta, caracteres visualmente idénticos en Unicode y otros caracteres que producirán resultados inesperados si los pasa directamente a TFileStream.Create. Ejecute siempre el nombre a través de ExtractFileName antes de construir cualquier ruta de salida, y considere rechazar nombres que comiencen con un punto o que contengan caracteres fuera de lo esperado por su sistema
La matriz de bytes devuelta por Attachment[Index] pertenece al llamador. Escríbala con un TFileStream común y podrá usarla como desee, incluyendo la inspección de los primeros bytes para verificar el formato real del archivo en lugar de confiar en el nombre declarado
procedure ExtractAttachment(Pdf: TPdf; Index: Integer; const OutputDir: string);
var
SafeName: string;
OutPath: string;
Data: TBytes;
FS: TFileStream;
begin
SafeName := ExtractFileName(Pdf.AttachmentName[Index]);
if SafeName = '' then
SafeName := Format('attachment_%d', [Index]);
OutPath := IncludeTrailingPathDelimiter(OutputDir) + SafeName;
Data := Pdf.Attachment[Index];
FS := TFileStream.Create(OutPath, fmCreate);
try
if Length(Data) > 0 then
FS.WriteBuffer(Data[0], Length(Data));
finally
FS.Free;
end;
end;
Añadir archivos adjuntos y la escritura en dos pasos
Crear un archivo adjunto requiere dos llamadas, no una. El método CreateAttachment(Name) registra un nuevo espacio en el árbol de archivos incrustados y devuelve True en caso de éxito. Ese espacio se inicia vacío. A continuación, asigna el contenido escribiendo en Attachment[AttachmentCount - 1], apuntando a la entrada creada más recientemente. Si CreateAttachment devuelve False, el espacio no se creó y la asignación corrompería el adjunto en el índice que se encuentre al final
Tras modificar la lista de adjuntos, los cambios residen únicamente en memoria. Llame a SaveAs para escribir un nuevo archivo con el árbol de archivos incrustados actualizado. El Componente PDFium no admite guardar en el mismo archivo actualmente abierto, debido a que el motor mantiene un controlador de lectura al origen. El patrón estándar para una actualización local (in-place) consiste en guardar en una ruta temporal, cerrar el documento, eliminar o renombrar el original, renombrar el archivo temporal a su posición definitiva y volver a abrirlo
procedure AddFileAttachment(Pdf: TPdf; const FilePath: string);
var
FS: TFileStream;
Data: TBytes;
AttachName: string;
begin
if not Pdf.Active then
Exit;
FS := TFileStream.Create(FilePath, fmOpenRead or fmShareDenyWrite);
try
SetLength(Data, FS.Size);
if FS.Size > 0 then
FS.ReadBuffer(Data[0], FS.Size);
finally
FS.Free;
end;
AttachName := ExtractFileName(FilePath);
if Pdf.CreateAttachment(AttachName) then
Pdf.Attachment[Pdf.AttachmentCount - 1] := Data;
end;
Información del tipo de archivo adjunto
Más allá del nombre y del contenido de bytes, AttachmentType[Index] devuelve la cadena de tipo MIME almacenada en el diccionario de archivos incrustados del PDF, si se registró alguna cuando se adjuntó el archivo originalmente. Muchos generadores dejan este campo vacío o lo establecen en un valor genérico como application/octet-stream, por lo que no puede confiar en él para la detección del formato en un proceso de producción. Para una identificación fiable, lea los primeros bytes del contenido y busque firmas de archivos conocidas: %PDF para un PDF anidado, la cabecera de archivo local ZIP PK\x03\x04 para documentos Office Open XML o \xD0\xCF\x11\xE0 para binarios heredados de archivos compuestos. La información de tipo del diccionario es adecuada para mostrarse en una etiqueta de interfaz de usuario, pero no debe guiar las decisiones de procesamiento cuando se dispone de los bytes reales
Eliminar archivos adjuntos
El método DeleteAttachment(Index) elimina la entrada en esa posición y devuelve True en caso de éxito. Después de la eliminación, las entradas restantes se desplazan hacia abajo, por lo que si va a eliminar múltiples adjuntos en un bucle debe iterar desde el último índice hacia abajo, no hacia adelante, para evitar omitir entradas después de cada desplazamiento. El cambio se realiza en memoria hasta que llame a SaveAs
Un escenario común en los procesos de tratamiento de documentos consiste en eliminar todos los adjuntos de un PDF entrante antes de pasarlo al siguiente nivel, por motivos de seguridad o tamaño. Cuente una vez antes del bucle e itere en orden inverso:
procedure StripAllAttachments(Pdf: TPdf);
var
I: Integer;
begin
for I := Pdf.AttachmentCount - 1 downto 0 do
Pdf.DeleteAttachment(I);
end;
Dónde aparecen los adjuntos de PDF en la práctica
La API de adjuntos funciona en cualquier PDF que PDFium pueda abrir, pero los documentos donde realmente encontrará archivos incrustados se concentran en unos pocos casos específicos. PDF/A-3 (ISO 19005-3) permite explícitamente archivos incrustados conformes como mecanismo para agrupar datos de origen junto con la versión de archivo; las facturas electrónicas ZUGFeRD y Factur-X se basan exactamente en esto para incrustar un contenido XML estructurado dentro del diseño PDF legible por humanos. Los PDFs derivados de correos electrónicos a veces contienen sus archivos adjuntos de mensajes originales redirigidos al árbol de archivos incrustados. La documentación técnica originada en sistemas de autoría estructurados a veces incluye recursos complementarios de la misma manera
Cuando su aplicación procesa PDFs entrantes de fuera de su organización, vale la pena comprobar AttachmentCount como parte de la recepción de documentos por dos razones independientes. En primer lugar, los archivos incrustados pueden contener datos que desee extraer y procesar, como el XML dentro de un PDF de factura. En segundo lugar, los archivos incrustados pueden contener contenido ejecutable arbitrario, por lo que saber qué hay presente es importante incluso si nunca tiene la intención de extraerlo. Ninguna de las dos razones le exige realizar nada complicado: lea la cantidad, compruebe los nombres y decida qué hacer con los bytes
Las propiedades de adjuntos mostradas aquí forman parte del Componente PDFium para Delphi y C++Builder