GroupDocs.Annotation for .NET adds markup to a
document and saves it back in its own format. There is no viewer to embed and no Adobe Acrobat or
Microsoft Office to install: it is a library that reads a file, adds annotations, and writes the
file out again — on Windows, on Linux and in a container.
This page is the shortest complete path through it: open a PDF, highlight a line, attach a comment
with a reply, read the markup back out, remove it, and render page images. The same code runs
unchanged on DOCX, XLSX, PPTX and images — only the file path changes.
Note
A runnable version of everything on this page is on GitHub:
annotate-pdf-csharp-net.
Clone it and run dotnet run --project src/AnnotatePdf -- info samples/quarterly-review.pdf.
Before you start
.NET 8.0 or later (the library also supports .NET Framework 4.6.2 and .NET 6).
The package from NuGet:
dotnet add package GroupDocs.Annotation
Install-PackageGroupDocs.Annotation
Without a licence the library runs in evaluation mode and the output carries evaluation marks.
Apply one at start-up — see Licensing:
Everything starts with an Annotator. It keeps the file open until it is disposed, so wrap it in a
using block — and note that it cannot save over the file it is reading. Save to a different path,
or to a temporary file you move into place afterwards.
usingGroupDocs.Annotation;using(Annotatorannotator=newAnnotator("quarterly-review.pdf")){// add, read or remove markup here}
Step 2: find the text you want to mark up
Coordinates can be hard-coded, but they rarely survive the next version of the document.
GetDocumentInfo() returns every page with its size and its text lines, each with a box, so the
phrase can be located instead of measured:
usingGroupDocs.Annotation;usingGroupDocs.Annotation.Models;IDocumentInfoinfo=annotator.Document.GetDocumentInfo();foreach(PageInfopageininfo.PagesInfo){foreach(TextLineInfolineinpage.TextLines){if(line.Text.Contains("Hosting costs")){// line.LeftIndent, line.TopIndent, line.Width, line.Height - in points, from the TOP-left}}}
PageInfo.Width and PageInfo.Height are the page size in points; TextLineInfo gives the box of
one line, measured from the top-left corner of the page.
Step 3: add a highlight with a comment and a reply
HighlightAnnotation.Points describes one or more rectangles. Four points make one rectangle, in
this order: top-left, top-right, bottom-left, bottom-right. The y values are measured from the
bottom-left corner — the convention PDF itself uses — so a line found in step 2 has to be
flipped:
usingSystem;usingSystem.Collections.Generic;usingGroupDocs.Annotation.Models;usingGroupDocs.Annotation.Models.AnnotationModels;floatleft=(float)line.LeftIndent;floatright=(float)(line.LeftIndent+line.Width);floattop=(float)(page.Height-line.TopIndent);floatbottom=(float)(page.Height-line.TopIndent-line.Height);HighlightAnnotationhighlight=newHighlightAnnotation{PageNumber=page.PageNumber-1,// PageInfo is 1-based, annotations are 0-basedBackgroundColor=0xFFE699,// 0xRRGGBBOpacity=0.5,Message="Check this against the invoice",CreatedOn=DateTime.Now,User=newUser{Id=1,Name="Reviewer",Role=Role.Editor},Points=newList<Point>{newPoint(left,top),newPoint(right,top),newPoint(left,bottom),newPoint(right,bottom)},Replies=newList<Reply>{newReply{Comment="Confirmed with finance.",RepliedOn=DateTime.Now}}};annotator.Add(highlight);
Message is the comment itself. Replies starts a thread: in Word the result is a comment with a
reply under it, in a PDF reader it is a note with a response. User is what lets a later pass find
and remove exactly this reviewer’s markup.
Page numbers and origins
Three of the four numbers above come from different conventions. Mixing them is the usual reason
markup appears on the wrong page or in the wrong place:
Member
Origin
Page numbering
PageInfo.PageNumber, TextLineInfo
top-left
1-based
AnnotationBase.PageNumber (every annotation type)
—
0-based
AreaAnnotation.Box, PointAnnotation.Box
top-left
—
HighlightAnnotation.Points and other text markup
bottom-left
—
PreviewOptions.PageNumbers
—
1-based
Warning
An annotation whose PageNumber is past the last page is dropped when the document is saved — no
exception is raised and no markup appears. If an annotation “did not show up”, check the page index
before anything else.
Step 4: save
annotator.Save("result.pdf");
Save writes the markup into the document in its own native form: real PDF annotations in a PDF,
real comments and highlighting in a Word file. Anyone can open the result in Acrobat, in Word or in
a browser without your application.
Pass SaveOptions
to write only some annotation types, only annotated pages, or a page range.
Get() reads the annotations the document actually contains, including ones a person added in
Word or Acrobat. Get(AnnotationType) narrows the result to one type.
Note
Id is assigned while the document is being read. It is stable for as long as the file does not
change, which is enough to delete or update an annotation in the same session — but it is not a key
to store in a database. To keep annotations outside the document, use
Export annotations and Import annotations.
Step 6: remove markup
using(Annotatorannotator=newAnnotator("result.pdf")){annotator.Remove(1);// by idannotator.Save("clean.pdf");}
To remove one reviewer’s markup and leave everyone else’s in place, filter by author: