Artigo Técnico

Importar e exportar anotações de PDF como XFDF no Delphi

O HotPDF exporta e importa anotações de PDF como XFDF por meio de duas funções que agem no documento carregado no momento: ExportLoadedAnnotationsToXFDF e ImportLoadedAnnotationsFromXFDF. O XFDF é o formato de intercâmbio de anotações XML padronizado como ISO 19444-1, e esse par permite que um programa Delphi ou C++Builder forneça seus comentários ao Acrobat ou a uma ferramenta de revisão de terceiros e receba de volta os resultados marcados, tudo sem reescrever o conteúdo da página onde as anotações estão

Imagine as duas direções que isso resolve. Um revisor abre seu relatório gerado no Acrobat, desenha uma seta vermelha em uma figura desalinhada, circula um total incorreto e digita uma nota na margem, e depois exporta os comentários para um pequeno arquivo XFDF. Ou o contrário: seu programa produz as marcações por si mesmo e você precisa enviá-las para alguém cuja ferramenta não seja o HotPDF. De qualquer forma, as anotações viajam como XML que ambos os lados entendem, e as páginas do PDF permanecem idênticas, byte a byte

Qual é a diferença entre FDF e XFDF?

O FDF e o XFDF transportam a mesma carga útil em duas sintaxes diferentes, e a distinção importa no momento em que você decide qual arquivo entregar a outra ferramenta. O FDF é o formato de dados de formulário (Forms Data Format) mais antigo definido dentro da própria especificação PDF: ele usa a sintaxe de objeto PDF, de modo que um arquivo FDF se parece com um PDF simplificado e precisa de um analisador compatível com PDF para ser lido. O XFDF é a expressão XML desses mesmos dados, padronizado de forma independente como ISO 19444-1, o que significa que qualquer biblioteca XML em qualquer plataforma pode abri-lo, compará-lo ou gerá-lo. Ambos os formatos podem carregar valores de campos de formulário em uma árvore <fields>, regida pela cláusula 6.3 da ISO 19444-1, e anotações em uma árvore <annots>; o HotPDF divide essas responsabilidades, direcionando os dados do formulário através do ExportLoadedFormToXFDF e reservando o ExportLoadedAnnotationsToXFDF para o lado de <annots>. Quando você troca comentários com um web service, um servidor de revisão Java ou um script, o XFDF é o formato que não obriga o outro lado a incorporar um analisador de PDF

Como exportar anotações de PDF como XFDF no Delphi?

O HotPDF exporta anotações percorrendo cada página do documento carregado, emitindo um elemento XFDF para cada anotação suportada e retornando o número de anotações gravadas. Carregue o PDF primeiro e depois chame o ExportLoadedAnnotationsToXFDF com um caminho de destino. O resultado inteiro é a contagem de anotações serializadas; um resultado igual ou inferior a zero significa que nada foi exportado e nenhum arquivo foi gerado, o que é o seu sinal de que o documento não continha anotações de um subtipo suportado

var
  Pdf: THotPDF;
  Written: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report-reviewed.pdf', '') > 0 then
    begin
      // Escreve um elemento XFDF por anotação suportada em cada página
      Written := Pdf.ExportLoadedAnnotationsToXFDF('comments.xfdf');
      if Written <= 0 then
        ShowMessage('Nenhuma anotação suportada foi encontrada');
    end;
  finally
    Pdf.Free;
  end;
end;

O XFDF gerado é um XML simples e legível. O HotPDF grava uma raiz <xfdf> no namespace ISO 19444-1, um contêiner <annots> e um filho para cada anotação com seu índice de página baseado em zero, cor e geometria como atributos ou elementos filhos. Uma linha com preenchimento amarelo e ponta de seta aberta, ao lado de um polígono preenchido, é serializada desta forma:

<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/">
  <annots>
    <line page="0" start="72,700" end="220,700"
          color="#FF0000" interior-color="#FFFF00"
          head="OpenArrow" tail="None">
      <contents-richtext>Baseline looks off</contents-richtext>
    </line>
    <polygon page="0" color="#0000FF" interior-color="#CCE5FF">
      <vertices>72,120;180,120;180,200;72,200</vertices>
    </polygon>
  </annots>
</xfdf>

Como os subtipos de anotação se mapeiam para elementos XFDF

Cada subtipo de anotação é mapeado para um elemento ISO 19444-1 específico com sua própria convenção de geometria, e o HotPDF segue essas estruturas em vez de inventar as suas próprias. Anotações de linha carregam os atributos start e end contendo os dois pares de coordenadas do ponto final, obtidos diretamente da matriz L da anotação, enquanto os estilos de fim de linha LE tornam-se os atributos head e tail. Anotações de polígono e polilinha movem sua lista de pontos para um elemento filho <vertices> como pares x,y separados por ponto e vírgula, e não como um atributo, porque um leitor que espera o elemento filho ignorará silenciosamente os pontos ocultos em qualquer outro lugar. Anotações de tinta (ink), que podem conter vários traços separados, aninham um elemento <inklist> com um filho <gesture> por traço, para que uma assinatura de vários traços sobreviva à viagem como gestos distintos em vez de uma única imagem mesclada

O estilo de rich text, cor e borda sobrevivem ao lado da geometria. O corpo de rich text de uma nota é gravado como um filho <contents-richtext>; o preenchimento interno que o PDF armazena na matriz IC, a pintura dentro de um círculo, quadrado, polígono ou ponta de seta de linha e o preenchimento de uma caixa de redação, vem como um atributo interior-color no formato #RRGGBB; e a largura da borda, o padrão de traços e um efeito de borda nublada são mapeados para os atributos width, dashes, style e intensity, de modo que uma chamada com contorno nublado ainda é lida como nublada no destino. O HotPDF também preserva a janela popup anexada a uma anotação de marcação, importando a geometria filha do popup e seu estado aberto ou fechado no dicionário Popup da anotação, e ele carrega os estados de abertura e revisão de anotação de texto, de modo que um documento revisado mantém não apenas suas formas, mas os metadados de fluxo de trabalho nos quais os revisores confiam

Importando XFDF de volta em um documento carregado

O HotPDF importa XFDF analisando o XML, criando uma nova anotação para cada elemento por meio do NewLoadedAnnotation, anexando-a à página indicada pelo elemento e retornando quantas anotações adicionou. O fluxo de trabalho é simétrico com a exportação: carregue o PDF base, chame o ImportLoadedAnnotationsFromXFDF com o arquivo do revisor e salve o documento carregado para persistir as novas marcações. Se o arquivo estiver ausente ou o XML não for analisado, a função retorna zero e o documento carregado permanece intocado

var
  Pdf: THotPDF;
  Added: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report.pdf', '') > 0 then
    begin
      Added := Pdf.ImportLoadedAnnotationsFromXFDF('comments.xfdf');
      if Added > 0 then
        Pdf.SaveLoadedDocument('report-annotated.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Como cada elemento XFDF indica seu próprio índice de página, as anotações caem nas páginas em que foram criadas, mesmo quando você importa vários arquivos em sequência, tornando seguro reunir comentários de mais de um revisor no mesmo documento carregado antes de salvá-lo. O exemplo abaixo une dois revisores em uma única cópia mesclada. Para construir e editar os próprios objetos de anotação no código, em vez de trocá-los como arquivos, veja como o HotPDF cria e edita objetos de anotação de PDF diretamente do Delphi

var
  Pdf: THotPDF;
  Total, I: Integer;
  Files: array[0..1] of string;
begin
  Files[0] := 'alice-comments.xfdf';
  Files[1] := 'bob-comments.xfdf';
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('master.pdf', '') > 0 then
    begin
      Total := 0;
      for I := Low(Files) to High(Files) do
        Inc(Total, Pdf.ImportLoadedAnnotationsFromXFDF(Files[I]));
      if Total > 0 then
        Pdf.SaveLoadedDocument('master-merged.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

O que faz a ida e volta de forma limpa e o que não faz

O HotPDF realiza a ida e volta dos subtipos de anotação que a norma ISO 19444-1 suporta, e ignora deliberadamente o restante para evitar a gravação de algo que um leitor interpretaria incorretamente. O conjunto suportado abrange os tipos de marcação que dominam o trabalho real de revisão: notas de texto, texto livre, linha, quadrado, círculo, polígono, polilinha, os quatro tipos de marcação de texto (destaque, sublinhado, tachado e ondulado), carimbo, tinta e cursor (caret), além de anexo de arquivo, som, redação e link, totalizando dezoito subtipos. Uma anotação cujo subtipo cai fora dessa lista é ignorada na exportação e, por ser omitida em vez de gravada vazia, ela não infla a contagem retornada pela função

O rich text é a ressalva honesta. O HotPDF preserva o corpo <contents-richtext> para que o texto estilizado e os conteúdos simples façam a viagem, mas o XFDF carrega o texto e a marcação de estilo de um comentário, não um fluxo de aparência renderizado; portanto, o aplicativo receptor redesenha o popup com suas próprias fontes e layout em vez de reproduzir os pixels exatos do HotPDF. Trate o processo de ida e volta como fiel ao conteúdo e à intenção, não à renderização exata na tela no nível do pixel. Se o seu conteúdo estilizado reside em dados de formulário XFA em vez de fluxos de anotação, as regras diferem, e o artigo sobre como o HotPDF trata exData XFA, rich text e hiperlinks cobre esse caminho separado

O tratamento no nível do caractere é mais rigoroso do que parece, que é exatamente o que você deseja. O HotPDF aplica as regras de escape da cláusula 5.8.2 da ISO 19444-1 quando grava texto, codificando os caracteres significativos do XML e os bytes de controle para que um comentário contendo um E comercial (ampersand), um colchete angular ou uma quebra de linha produza um XML bem-formado que qualquer analisador compatível aceite, e ele reverte as mesmas regras na importação. É por isso que uma nota copiada de uma planilha, com pontuação e tudo, retorna intacta em vez de corromper o arquivo

O intercâmbio de anotações é apenas uma parte do que a API de documento carregado faz e ele se combina com o resto: importe o XFDF de um revisor, ajuste as páginas ou edite os metadados do documento, achate ou redefina as permissões do arquivo, e depois exporte um XFDF atualizado para a próxima rodada. Tudo isso é fornecido no Componente HotPDF padrão para Delphi e C++Builder, cuja referência documenta a cobertura completa de subtipos de anotação e as funções XFDF de dados de formulário complementares