💡 Exemplo completo em funcionamento disponível no GitHub:
extract-annotations-from-pdf-using-groupdocs-parser-dotnet

Introdução

Um PDF que passou por revisão geralmente contém mais do que seu texto visível – notas adesivas, observações destacadas e comentários embutidos deixados pelos revisores. Percorrer página por página para encontrá‑los não escala quando um documento passa por várias rodadas de feedback. GroupDocs.Parser é uma biblioteca .NET que lê programaticamente as anotações incorporadas de um documento, transformando comentários dispersos dos revisores em dados estruturados que seu código pode manipular. Este tutorial mostra como extrair anotações de um PDF inteiro, detalhá‑las página por página, obtê‑las junto ao texto do documento e exportar os resultados para CSV ou JSON.

Encontrei esse problema ao criar um rastreador de revisões para uma equipe de documentação: uma nota de lançamento de 40 páginas havia passado por três revisores, e abrir o arquivo manualmente para encontrar cada comentário demorava mais do que realmente corrigir os problemas apontados. Extrair as anotações em poucas linhas de código transformou isso em uma tarefa de dois minutos.

Nas seções a seguir você aprenderá a:

  • Extrair todas as anotações de um PDF em uma única passagem.
  • Marcar cada anotação com a página à qual ela pertence.
  • Obter texto do documento e texto da anotação juntos em uma única leitura.
  • Serializar os resultados para CSV ou JSON para ferramentas downstream.

Por que extrair anotações de PDF é importante

Ler anotações de PDF programaticamente é útil para:

  • Fluxos de revisão: Coletar cada comentário do revisor sem abrir o arquivo em um visualizador de PDF.
  • Colaboração: Exibir seções destacadas ou anotadas diretamente dentro de suas próprias ferramentas.
  • Auditoria: Manter um registro das marcações deixadas em um documento ao longo do tempo, mesmo depois de ele ser achatado ou finalizado.

GroupDocs.Parser adicionou extração nativa de anotações para documentos PDF na versão 26.7 através do método GetAnnotations, juntamente com a nova opção IncludeAnnotations em TextOptions para inserir o texto da anotação em uma leitura de texto regular.

Pré-requisitos

  • .NET 6.0 ou superior
  • GroupDocs.Parser for .NET 26.7+ (licença temporária)
  • Um arquivo PDF com anotações existentes (por exemplo, document-with-annotations.pdf)

Instale via NuGet:

dotnet add package GroupDocs.Parser

Como extrair anotações de um documento PDF?

Resposta: Carregue o arquivo com Parser, então chame GetAnnotations() para o documento inteiro ou GetAnnotations(pageIndex) para uma única página. Cada resultado é uma coleção de objetos AnnotationItem cujo atributo Value contém o texto do comentário. Se preferir ver os comentários embutidos no conteúdo regular do documento, defina IncludeAnnotations em TextOptions e chame GetText em vez disso.

Extração de Documento Inteiro

O trecho a seguir extrai todas as anotações do arquivo em uma única chamada, que é a forma mais rápida de verificar se um documento possui comentários abertos.

// Extract every annotation from the whole document
var result = new List<string>();
using (var parser = new Parser(path))
{
    IEnumerable<AnnotationItem> annotations = parser.GetAnnotations();
    if (annotations == null)
    {
        return result; // format doesn't support annotations
    }

    foreach (var item in annotations)
    {
        result.Add(item.Value); // annotation text
    }
}
return result;

Pontos‑chave:

  • GetAnnotations() retorna null quando a extração de anotações não é suportada para o documento, e uma coleção vazia quando o documento simplesmente não possui nenhuma.
  • Cada AnnotationItem expõe seu texto através da propriedade Value – esse é o único dado que o SDK atualmente relata.
  • Nenhuma atribuição de página é incluída aqui; use a sobrecarga por página abaixo se precisar disso.

Extração por Página

Quando a localização de um comentário importa, percorra as páginas do documento e chame GetAnnotations(pageIndex) para cada uma.

// Tag each annotation with its zero-based page index
var result = new List<AnnotationRecord>();
using (var parser = new Parser(path))
{
    if (!parser.Features.Annotations)
    {
        return result;
    }

    var info = parser.GetDocumentInfo();
    if (info == null || info.PageCount == 0)
    {
        return result;
    }

    for (int pageIndex = 0; pageIndex < info.PageCount; pageIndex++)
    {
        IEnumerable<AnnotationItem> pageAnnotations = parser.GetAnnotations(pageIndex);
        if (pageAnnotations == null)
        {
            continue;
        }

        foreach (var item in pageAnnotations)
        {
            result.Add(new AnnotationRecord { PageIndex = pageIndex, Value = item.Value });
        }
    }
}
return result;

Pontos‑chave:

  • GetDocumentInfo().PageCount controla o laço; não há um “contagem de páginas de anotação” separada.
  • GetAnnotations(pageIndex) usa um índice zero‑based, correspondendo a todos os outros métodos de nível de página da API.
  • A lista resultante de AnnotationRecord tem exatamente o formato que uma exportação CSV ou JSON precisa.

Extraindo Texto Junto com Anotações

Em vez de duas passagens sobre o documento, você pode incorporar o texto da anotação diretamente na saída da extração de texto regular.

// Read document text with annotation text included
using (var parser = new Parser(path))
{
    var options = new TextOptions
    {
        IncludeAnnotations = true
    };

    using (TextReader reader = parser.GetText(options))
    {
        return reader?.ReadToEnd() ?? string.Empty;
    }
}

Pontos‑chave:

  • IncludeAnnotations é uma propriedade em TextOptions, portanto funciona com a mesma chamada GetText que você já usa para extração de texto simples.
  • Útil quando você deseja uma saída única no estilo transcrição, em vez de uma lista separada de comentários.
  • Combine com GetText(pageIndex, options) se precisar disso apenas para uma página.

Verificando o Suporte a Anotações Primeiro

Nem todo formato suporta anotações, então vale a pena verificar antes de construir lógica em torno de GetAnnotations.

// Returns true if the loaded document format supports annotation extraction
using (var parser = new Parser(path))
{
    return parser.Features.Annotations;
}

Pontos‑chave:

  • Features.Annotations é um simples flag booleano na instância Parser.
  • Verificá‑lo antecipadamente torna a intenção explícita, embora GetAnnotations já falhe graciosamente retornando null.

Exportando as Anotações para CSV

Uma exportação CSV permite que revisores abram a lista de comentários diretamente no Excel. O método abaixo grava um arquivo de duas colunas (page,value) a partir dos registros marcados por página criados anteriormente.

var sb = new StringBuilder();
sb.AppendLine("page,value");

foreach (var record in records)
{
    sb.AppendLine($"{record.PageIndex},{CsvEscape(record.Value)}");
}

File.WriteAllText(outputPath, sb.ToString());

Pontos‑chave:

  • CsvEscape coloca aspas de forma segura em campos que contêm vírgulas, aspas ou quebras de linha.
  • O arquivo resultante abre diretamente no Excel ou pode ser encaminhado para uma ferramenta de tickets.

Helper: CsvEscape

if (string.IsNullOrEmpty(s)) return string.Empty;
if (s.Contains(",") || s.Contains("\"") || s.Contains("\n"))
{
    return "\"" + s.Replace("\"", "\"\"") + "\"";
}
return s;

Exportando as Anotações para JSON

Para pipelines que consomem comentários programaticamente, um array JSON costuma ser mais adequado que um CSV plano.

var sb = new StringBuilder();
sb.AppendLine("[");

for (int i = 0; i < records.Count; i++)
{
    var comma = i < records.Count - 1 ? "," : string.Empty;
    sb.AppendLine($"  {{ \"page\": {records[i].PageIndex}, \"value\": \"{Escape(records[i].Value)}\" }}{comma}");
}

sb.AppendLine("]");
File.WriteAllText(outputPath, sb.ToString());

Pontos‑chave:

  • A saída é um array plano de objetos { page, value } – fácil de desserializar por qualquer serviço downstream.
  • Escape mantém a carga válida JSON sem precisar de uma biblioteca de serialização.

Helper: Escape

return s?.Replace("\\", "\\\\").Replace("\"", "\\\"") ?? string.Empty;

Comparando Métodos: Quando Usar Cada Um

Método Melhor Para Principais Vantagens Limitações
Extração de Documento Inteiro Verificação rápida de “há comentários?” Chamada única, código mais simples Sem atribuição de página
Extração por Página Roteamento de feedback para a seção correta Resultados marcados por página, prontos para exportar Uma chamada extra por página
Texto Combinado + Anotações Transcrição única legível Nenhuma segunda passagem sobre o documento Comentários não são separados do texto principal
Exportação CSV Rastreamento de revisão baseado em planilhas Fácil de abrir no Excel, legível por humanos Limitado a estrutura plana
Exportação JSON Pipelines automatizados, sistemas de tickets Estruturado, legível por máquina Carga útil um pouco maior

Comece com a extração de documento inteiro para confirmar se um arquivo tem comentários que valem a pena ser tratados, depois passe para a extração por página quando precisar direcionar o feedback a uma seção específica.

Melhores Práticas e Dicas

  • Dispose Parser prontamente: envolva‑o em um bloco using para liberar recursos nativos.
  • Distinga null de vazio: GetAnnotations retornando null significa que o formato não é suportado; uma coleção vazia indica que o documento não tem comentários.
  • Verifique Features.Annotations em jobs em lote: ignore arquivos não suportados logo no início, em vez de depender de uma verificação null profunda no seu laço.
  • Reutilize a lista marcada por página: construa‑a uma vez com ExtractAnnotationsByPage e alimente tanto o exportador CSV quanto o JSON a partir dos mesmos dados, assim as duas saídas nunca divergem.
  • Segurança: o texto da anotação é entrada livre do revisor – trate‑o como qualquer outra string não confiável antes de renderizá‑lo em UI ou relatório.

Conclusão

GroupDocs.Parser oferece uma forma direta e programática de puxar comentários de revisores de um PDF em vez de caçá‑los manualmente. Ao extrair anotações para o documento inteiro, marcá‑las por página ou incorporá‑las ao fluxo de texto regular, você pode construir fluxos de revisão que exibem feedback no momento em que o documento entra na sua pipeline. Exporte os resultados para CSV ou JSON e conecte‑os diretamente às ferramentas que sua equipe já usa.

Próximos passos:

Recursos Adicionais