A maioria das respostas a esta pergunta tem cinco anos, funciona apenas com PDF e começa pedindo que você meça coordenadas em uma página que não pode ver. Aqui está a versão de 2026: sete chamadas, as duas convenções que decidem onde a marcação realmente aparece e a única linha que um contêiner Linux precisa.
Tudo abaixo funciona em .NET 8 e .NET 10, no Windows e no Linux, sem Adobe Acrobat e sem Microsoft Office instalados — e o mesmo código funciona em DOCX, XLSX, PPTX e imagens. Apenas o caminho do arquivo muda.
A versão executável está no GitHub:
annotate-pdf-csharp-net.
Instalar
dotnet add package GroupDocs.Annotation
Sem uma licença a biblioteca roda em modo de avaliação e a saída traz marcas de avaliação. Uma
licença temporária as remove:
new License().SetLicense("GroupDocs.Annotation.lic");
Abrir o documento
using (Annotator annotator = new Annotator("quarterly-review.pdf"))
{
// add, read or remove markup here
}
Uma coisa a saber de antemão: o anotador mantém o arquivo aberto até ser descartado, e não pode salvar sobre o arquivo que está lendo. Salve em um caminho diferente, ou em um arquivo temporário que você move para o local depois.
Não meça coordenadas — procure o texto
Esta é a parte que a maioria dos tutoriais ignora, e é a que faz o código de anotação sobreviver à próxima versão do documento. GetDocumentInfo() devolve cada página com seu tamanho e suas linhas de texto, cada uma com uma caixa:
IDocumentInfo info = annotator.Document.GetDocumentInfo();
foreach (PageInfo page in info.PagesInfo)
{
foreach (TextLineInfo line in page.TextLines)
{
if (line.Text.Contains("Hosting costs"))
{
// line.LeftIndent, line.TopIndent, line.Width, line.Height — points, from the top-left
}
}
}
Agora a anotação pode ser colocada onde o documento indica hoje, em vez de onde ele dizia no ano passado.
Adicionar um destaque com comentário e resposta
float left = (float)line.LeftIndent;
float right = (float)(line.LeftIndent + line.Width);
float top = (float)(page.Height - line.TopIndent);
float bottom = (float)(page.Height - line.TopIndent - line.Height);
annotator.Add(new HighlightAnnotation
{
PageNumber = page.PageNumber - 1,
BackgroundColor = 0xFFE699,
Opacity = 0.5,
Message = "Check this against the invoice",
CreatedOn = DateTime.Now,
User = new User { Id = 1, Name = "Reviewer", Role = Role.Editor },
Points = new List<Point>
{
new Point(left, top), new Point(right, top),
new Point(left, bottom), new Point(right, bottom)
},
Replies = new List<Reply>
{
new Reply { Comment = "Confirmed with finance.", RepliedOn = DateTime.Now }
}
});
annotator.Save("result.pdf");
Message é o comentário. Replies inicia um thread — em um leitor de PDF é uma nota com resposta, no Word é um comentário com uma resposta abaixo dele. User permite que uma passagem posterior encontre e remova exatamente a marcação desse revisor e deixe as dos demais intocadas.
As duas convenções que pegam todo mundo
Observe atentamente as duas linhas que calculam top e bottom, e o PageNumber.
Os números de página são contados duas vezes. PageInfo.PageNumber é baseado em 1.
AnnotationBase.PageNumber é baseado em 0. Misturá‑los faz com que toda anotação fique deslocada em uma página — e as que deveriam aparecer na última página desaparecem, porque uma anotação direcionada a uma página que não existe é descartada ao salvar, sem exceção e sem aviso. Se a marcação “não apareceu”, verifique o índice da página antes de qualquer outra coisa.
Duas origens. TextLineInfo e AreaAnnotation.Box são medidos a partir do canto superior‑esquerdo. Marcação de texto — destaque, sublinhado, tachado, ondulado — é medida a partir do canto inferior‑esquerdo, que é como o PDF funciona.
| Membro | Origem | Numeração de página |
|---|---|---|
PageInfo, TextLineInfo |
top-left | 1-based |
AnnotationBase.PageNumber |
— | 0-based |
AreaAnnotation.Box |
top-left | — |
HighlightAnnotation.Points |
bottom-left | — |
PreviewOptions.PageNumbers |
— | 1-based |
Quatro pontos formam um retângulo, nesta ordem: superior‑esquerdo, superior‑direito, inferior‑esquerdo, inferior‑direito. Mais retângulos podem seguir na mesma lista, que é como um único comentário cobre uma frase que se estende por três linhas.
Ler a marcação de volta
using (Annotator annotator = new Annotator("result.pdf"))
{
foreach (AnnotationBase a in annotator.Get())
{
Console.WriteLine($"#{a.Id} {a.Type} on page {a.PageNumber + 1}: {a.Message}");
foreach (Reply reply in a.Replies)
Console.WriteLine($" reply: {reply.Comment}");
}
}
Get() lê o que o documento realmente contém — incluindo anotações que uma pessoa adicionou no Word ou no Acrobat, o que faz uma ida e volta por outras ferramentas funcionar.
Um detalhe importante: Id é atribuído enquanto o documento está sendo lido, não armazenado nele. É estável o suficiente para excluir ou atualizar uma anotação na mesma sessão, e não é uma chave para colocar em um banco de dados. Para manter anotações fora do arquivo, use importação e exportação.
Remover novamente
annotator.Remove(1); // by id
annotator.Remove(annotator.Get()
.Where(a => a.User?.Name == "Reviewer").ToList()); // or by author
Renderizar as páginas
PreviewOptions previewOptions = new PreviewOptions(n => File.Create($"page_{n}.png"));
previewOptions.PreviewFormat = PreviewFormats.PNG;
previewOptions.PageNumbers = new[] { 1, 2 }; // 1-based
previewOptions.RenderAnnotations = true;
previewOptions.Resolution = 144;
annotator.Document.GeneratePreview(previewOptions);
Útil em três situações: uma UI web que mostra o documento, um teste que comprova que a marcação chegou onde você pretendia, e um agente que precisa ver seu próprio trabalho.
No Linux
O código não muda. O contêiner muda:
FROM mcr.microsoft.com/dotnet/runtime:8.0
RUN apt-get update \
&& apt-get install -y --no-install-recommends fontconfig fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
As imagens de runtime do .NET são distribuídas sem fontes e a renderização de texto precisa delas. Essa única linha faz a diferença entre saída correta e páginas em branco — e é a pergunta mais comum sobre bibliotecas de documentos no Linux em geral.
Para onde ir a seguir
git clone https://github.com/groupdocs-annotation/annotate-pdf-csharp-net.git
cd annotate-pdf-csharp-net
dotnet run --project src/AnnotatePdf -- highlight samples/quarterly-review.pdf --text "Hosting costs"
- Documentação: Annotate a PDF in C#
- Deixe um LLM revisar um contrato e deixar comentários reais — as mesmas chamadas, acionadas por um modelo
- Construa uma UI de anotação no navegador — as mesmas chamadas, acionadas por uma pessoa no navegador