Les pièces jointes de fichiers PDF sont stockées dans l'arborescence des fichiers intégrés du document, une structure que la plupart des visionneuses affichent sous la forme d'un panneau trombone ou d'une barre latérale de pièces jointes. Depuis le code Delphi, le composant PDFium expose cette arborescence à travers un ensemble restreint de propriétés indexées sur TPdf : vous effectuez l'itération par index entier, lisez les noms et les charges utiles d'octets, créez de nouveaux emplacements et supprimez les existants. L'interface de l'API est étroite ; il y a seulement quelques contraintes d'ordre et une règle de nettoyage à connaître avant d'écrire du code de production à ce sujet
Lire les pièces jointes depuis un document ouvert
AttachmentCount indique le nombre de fichiers intégrés déclarés par le document. Cette propriété est lue directement à partir de l'appel sous-jacent de PDFium, de sorte qu'elle reflète uniquement ce que le PDF contient réellement. À partir de là, AttachmentName[Index] renvoie le nom d'affichage sous la forme d'une WString, et Attachment[Index] fournit les octets bruts sous la forme d'un tableau TBytes. Les deux sont basés sur un index à partir de zéro. Le document doit être ouvert (Pdf.Active = True) avant d'interroger l'une ou l'autre de ces propriétés ; les appeler sur un document fermé renvoie zéro ou un résultat vide, sans exception
Un point est important à garder à l'esprit : Attachment[Index] alloue et renvoie la charge utile complète du fichier à chaque lecture. Pour un document contenant un fichier intégré volumineux, parcourir toutes les pièces jointes pour construire une liste d'affichage implique de payer ce coût d'allocation à chaque appel. Si vous n'avez besoin que des noms pour l'affichage, lisez d'abord AttachmentName et différez la récupération des octets jusqu'à ce que l'utilisateur demande réellement le fichier
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;
Extraire une pièce jointe sur le disque
Il n'y a pas d'assistant SaveAttachment. Vous lisez les octets et les écrivez là où vous en avez besoin, ce qui laisse la construction et la validation du chemin sous la responsabilité de votre code. Cet aspect est important lorsque les noms de pièces jointes proviennent de documents non approuvés. Les noms des pièces jointes PDF sont des chaînes stockées à l'intérieur du fichier ; ils peuvent contenir des séparateurs de chemin, des caractères Unicode trompeurs et d'autres caractères susceptibles de produire des résultats inattendus si vous les passez directement à TFileStream.Create. Passez toujours le nom par la fonction ExtractFileName avant de construire tout chemin de sortie, et envisagez de rejeter les noms commençant par un point ou contenant des caractères non pris en charge par votre système
Le tableau d'octets renvoyé par Attachment[Index] appartient à l'appelant. Écrivez-le avec un TFileStream classique et vous pourrez l'utiliser à votre convenance, y compris pour inspecter les premiers octets afin de valider le format réel du fichier plutôt que de vous fier au nom déclaré
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;
Ajouter des pièces jointes et l'écriture en deux étapes
La création d'une pièce jointe nécessite deux appels, et non un seul. CreateAttachment(Name) enregistre un nouvel emplacement dans l'arborescence des fichiers intégrés et renvoie True en cas de succès. Cet emplacement commence vide. Vous attribuez ensuite la charge utile en écrivant dans Attachment[AttachmentCount - 1], ciblant ainsi l'entrée la plus récemment créée. Si CreateAttachment renvoie False, l'emplacement n'a pas été créé et l'affectation corromprait la pièce jointe située au dernier index existant
Après modification de la liste des pièces jointes, les changements résident uniquement en mémoire. Appelez SaveAs pour écrire un nouveau fichier contenant l'arborescence des fichiers intégrés mise à jour. Le composant PDFium ne prend pas en charge la sauvegarde dans le fichier actuellement ouvert, car le moteur conserve un accès en lecture sur la source. Le modèle standard pour une mise à jour sur place consiste à enregistrer dans un chemin temporaire, fermer le document, supprimer ou renommer l'original, puis renommer le fichier temporaire à sa place et le réouvrir
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;
Informations sur le type de pièce jointe
En plus du nom et de la charge utile d'octets, AttachmentType[Index] renvoie la chaîne de type MIME stockée dans le dictionnaire des fichiers intégrés du PDF, si elle a été enregistrée lors de l'intégration initiale du fichier. De nombreux générateurs laissent ce champ vide ou le définissent sur une valeur générique telle que application/octet-stream, vous ne pouvez donc pas vous y fier pour détecter le format dans un processus de production. Pour une identification fiable, lisez les premiers octets de la charge utile et recherchez les signatures de fichiers connues : %PDF pour un PDF imbriqué, l'en-tête de fichier local ZIP PK\x03\x04 pour les documents Office Open XML, ou \xD0\xCF\x11\xE0 pour les fichiers binaires composites hérités. Les informations de type issues du dictionnaire conviennent pour l'affichage d'un libellé dans l'interface utilisateur, mais ne doivent pas dicter les choix de traitement lorsque vous disposez des octets réels
Supprimer des pièces jointes
DeleteAttachment(Index) supprime l'entrée à cette position et renvoie True en cas de succès. Après la suppression, les entrées restantes sont décalées vers le bas. Si vous supprimez plusieurs pièces jointes dans une boucle, vous devez donc effectuer l'itération en partant du dernier index vers le bas, et non vers le haut, afin d'éviter de sauter des entrées après chaque décalage. La modification reste en mémoire jusqu'à ce que vous appeliez SaveAs
Un scénario classique dans les processus de traitement de documents consiste à retirer toutes les pièces jointes d'un PDF entrant avant de le transmettre en aval, pour des raisons de sécurité ou de taille. Calculez le nombre d'entrées une fois avant la boucle et itérez en sens inverse :
procedure StripAllAttachments(Pdf: TPdf);
var
I: Integer;
begin
for I := Pdf.AttachmentCount - 1 downto 0 do
Pdf.DeleteAttachment(I);
end;
Où apparaissent les pièces jointes PDF en pratique
L'API de pièces jointes fonctionne sur tout PDF que PDFium peut ouvrir, mais les documents dans lesquels vous rencontrez réellement des fichiers intégrés se limitent à quelques cas spécifiques. La norme PDF/A-3 (ISO 19005-3) autorise explicitement les fichiers intégrés conformes comme mécanisme pour regrouper les données sources aux côtés de la version d'archivage ; les factures électroniques ZUGFeRD et Factur-X s'appuient précisément sur cela pour intégrer une charge utile XML structurée au sein de la mise en page PDF lisible par l'homme. Les PDF issus de courriels contiennent parfois les pièces jointes du message d'origine transférées dans l'arborescence des fichiers intégrés. Les documentations techniques issues de systèmes de rédaction structurée regroupent parfois des ressources de support de la même manière
Lorsque votre application traite des PDF entrants provenant de l'extérieur de votre organisation, il est utile de vérifier AttachmentCount lors de la réception du document pour deux raisons distinctes. Premièrement, les fichiers intégrés peuvent contenir des données que vous souhaitez extraire et traiter, comme le XML d'une facture PDF. Deuxièmement, les fichiers intégrés peuvent transporter du contenu exécutable arbitraire, de sorte qu'il est important de savoir ce qui est présent même si vous n'avez pas l'intention de l'extraire. Aucune de ces raisons ne requiert de traitement complexe : lisez le décompte, vérifiez les noms et décidez de l'action à mener sur les octets
Les propriétés de pièces jointes présentées ici font partie du composant PDFium pour Delphi et C++Builder