Os anexos de arquivos PDF são armazenados na árvore de arquivos incorporados do documento, uma estrutura que a maioria dos visualizadores apresenta como um painel de clipe de papel ou uma barra lateral de anexos. A partir do código Delphi, o PDFium Component expõe essa árvore por meio de um pequeno conjunto de propriedades indexadas no TPdf: você itera pelo índice inteiro, lê nomes e cargas de bytes, cria novos slots e exclui os existentes. A interface da API é estreita; existem apenas algumas restrições de ordenação e uma regra de higienização que vale a pena conhecer antes de escrever o código de produção correspondente
Lendo anexos a partir de um documento aberto
O AttachmentCount fornece o número de arquivos incorporados que o documento declara. Ele lê diretamente da chamada subjacente do PDFium, portanto, reflete apenas o que o PDF realmente contém. A partir daí, o AttachmentName[Index] retorna o nome de exibição como um WString, e o Attachment[Index] entrega os bytes brutos como uma matriz TBytes. Ambos são baseados em zero. O documento deve estar aberto (Pdf.Active = True) antes de você consultar qualquer uma das propriedades; chamá-las em um documento fechado retorna zero ou um resultado vazio sem gerar exceções
Uma coisa a ter em mente: o Attachment[Index] aloca e retorna a carga completa do arquivo em cada leitura. Para um documento que carrega um ativo incorporado grande, iterar por todos os anexos para construir uma lista de exibição significa pagar esse custo de alocação em cada chamada. Se você precisar apenas dos nomes para fins de exibição, leia primeiro o AttachmentName e adie a busca de bytes até que o usuário realmente solicite o arquivo
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;
Extraindo um anexo para o disco
Não há um método auxiliar SaveAttachment. Você lê os bytes e os grava onde for necessário, o que coloca a construção e a higienização do caminho inteiramente sob a responsabilidade do seu código. Isso é importante quando os nomes dos anexos vêm de documentos não confiáveis. Os nomes de anexos de PDF são strings armazenadas dentro do arquivo; eles podem conter separadores de caminho, caracteres Unicode semelhantes e outros caracteres que produzirão resultados inesperados se você os passar diretamente para o TFileStream.Create. Sempre passe o nome por ExtractFileName antes de criar qualquer caminho de saída, e considere rejeitar nomes que comecem com um ponto ou conterem caracteres fora do esperado pelo seu sistema
A matriz de bytes retornada por Attachment[Index] pertence ao chamador. Grave-a com um TFileStream normal e ela será sua para fazer o que desejar, incluindo inspecionar os primeiros bytes para verificar o formato real do arquivo, em vez de confiar no nome 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;
Adicionando anexos e a gravação em duas etapas
Criar um anexo requer duas chamadas, não uma. O CreateAttachment(Name) registra um novo slot na árvore de arquivos incorporados e retorna True em caso de sucesso. Esse slot começa vazio. Você então atribui a carga gravando no Attachment[AttachmentCount - 1], visando a entrada criada mais recentemente. Se o CreateAttachment retornar False, o slot não foi criado e a atribuição corromperia o anexo em qualquer índice que estivesse por último
Depois de modificar a lista de anexos, as alterações residem apenas na memória. Chame o SaveAs para gravar um novo arquivo com a árvore de arquivos incorporados atualizada. O PDFium Component não suporta salvar de volta no mesmo arquivo que está aberto no momento, porque o mecanismo mantém um identificador de leitura para a origem. O padrão comum para uma atualização local é salvar em um caminho temporário, fechar o documento, excluir ou renomear o original, depois renomear o arquivo temporário para a posição correta e reabrir
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;
Informações sobre o tipo de anexo
Além do nome e da carga de bytes, o AttachmentType[Index] retorna a string de tipo MIME armazenada no dicionário de arquivos incorporados do PDF, se alguma foi registrada quando o arquivo foi originalmente anexado. Muitos geradores deixam esse campo vazio ou o definem com um valor genérico como application/octet-stream, por isso você não pode confiar nele para detecção de formato em um pipeline de produção. Para uma identificação confiável, leia os primeiros bytes da carga e verifique assinaturas de arquivo conhecidas: %PDF para um PDF aninhado, o cabeçalho de arquivo local ZIP PK\x03\x04 para documentos Office Open XML, \xD0\xCF\x11\xE0 para binários legados de arquivos compostos. As informações de tipo do dicionário são boas para exibir em um rótulo de interface do usuário, mas não devem orientar decisões de processamento quando você tem os bytes reais disponíveis
Excluindo anexos
O DeleteAttachment(Index) remove a entrada nessa posição e retorna True em caso de sucesso. Após a exclusão, as entradas restantes são deslocadas para baixo, portanto, se você estiver excluindo vários anexos em um loop, deve iterar do último índice para baixo, e não para a frente, para evitar pular entradas após cada deslocamento. A alteração permanece na memória até que você chame o SaveAs
Um cenário comum em pipelines de processamento de documentos é remover todos os anexos de um PDF recebido antes de passá-lo adiante, por motivos de segurança ou tamanho. Conte uma vez antes do loop e itere em ordem inversa:
procedure StripAllAttachments(Pdf: TPdf);
var
I: Integer;
begin
for I := Pdf.AttachmentCount - 1 downto 0 do
Pdf.DeleteAttachment(I);
end;
Onde os anexos de PDF aparecem na prática
A API de anexos funciona em qualquer PDF que o PDFium consiga abrir, mas os documentos onde você realmente encontra arquivos incorporados se concentram em alguns casos específicos. O PDF/A-3 (ISO 19005-3) permite explicitamente arquivos incorporados conformes como um mecanismo para agrupar dados de origem junto com a renderização de arquivamento; as faturas eletrônicas ZUGFeRD e Factur-X contam exatamente com esse mecanismo para incorporar uma carga estruturada de XML dentro do layout de PDF legível por humanos. PDFs derivados de e-mail às vezes carregam seus anexos de mensagens originais encaminhados na árvore de arquivos incorporados. A documentação técnica originada em sistemas de autoria estruturada ocasionalmente agrupa ativos de suporte da mesma maneira
Quando a sua aplicação processa PDFs recebidos de fora da sua organização, vale a pena verificar o AttachmentCount como parte da entrada do documento por dois motivos independentes. Primeiro, os arquivos incorporados podem conter dados que você deseja extrair e processar, como o XML dentro de um PDF de fatura. Segundo, os arquivos incorporados podem carregar conteúdo executável arbitrário, de modo que saber o que está presente é importante, mesmo quando você nunca pretende extraí-lo. Nenhum dos motivos exige que você faça algo complexo: leia a contagem, verifique os nomes e decida o que fazer com os bytes
As propriedades de anexos mostradas aqui fazem parte do PDFium Component para Delphi e C++Builder