Skip to content
List Annotations

List Annotations

page.GetAnnotations() returns the annotations attached to a page, in the order they appear in the page’s /Annots array. Use it to inspect a file before modifying it: to find out whether a review comment is already there, to locate the form widgets, or to check what a third-party tool wrote into the document.

Each element is a *model.PdfAnnotation holding the entries common to every annotation subtype. The subtype-specific fields live in a separate struct reachable through GetContext(), so identifying an annotation means type-switching on that value rather than reading a subtype field.

Doing it

page, err := pdfReader.GetPage(pageNum)
if err != nil {
    return err
}

annotations, err := page.GetAnnotations()
if err != nil {
    return err
}

for _, annot := range annotations {
    switch a := annot.GetContext().(type) {
    case *model.PdfAnnotationSquare:
        fmt.Printf("square, color %v\n", a.C)
    case *model.PdfAnnotationText:
        fmt.Printf("sticky note: %v\n", a.Contents)
    }
}

annot.String() is the quick alternative when you just want to look at a file. It prints the Go type of the context followed by the raw annotation dictionary, which is where the *model.PdfAnnotationSquare: Dict(...) lines in the output below come from.

The common entries are on the outer PdfAnnotation: Rect, Contents, AP, C, Border, F. They are core.PdfObject values, not Go primitives, so unwrap them with the core helpers (core.GetNumbersAsFloat, core.GetStringVal) before doing arithmetic on them. The embedded struct means a.Rect works from the context value too.

Limitations

A page with no /Annots array returns nil, nil, and so does a page not attached to a reader, since there is nothing to resolve and nobody to resolve it against. A nil slice is not an error. Annotations you added yourself with AddAnnotation are returned either way, because they go into the same cached slice the load fills.

Annotation subtypes UniPDF does not model are dropped from the result. The reader logs Ignoring unknown annotation at debug level and omits the entry, so the returned slice can be shorter than the /Annots array. Count the array yourself if you need the true number.

GetContext() returns nil for an annotation whose dictionary has no /Subtype, which some generators omit. The annotation is still returned with its common entries intact, so a type switch over contexts needs to tolerate a nil case.

Two errors come from a malformed file rather than from your code: Annots not an array when /Annots is some other object, and annotation not in an indirect object when an element is neither a dictionary nor a reference to one. An /Annots entry that is the null object is skipped without complaint.

Results are cached on the page after the first call, so repeated calls are cheap and return the same slice. Widget annotations get linked to their form field during that first load, which is what makes PdfAnnotationWidget usable from the forms guides.

Run the example

The example takes one or more input files on the command line and prints a numbered listing per page. listAnnotations does the reading; printAnnotations does the formatting.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/annotations
go run pdf_list_annotations.go input.pdf

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source

Sample output

Run against a document carrying a single square annotation:

Page with a square annotation

-- Page 1
 1. *model.PdfAnnotationSquare: Dict("Type": Annot, "Rect": [139, 450, 199, 490], "AP": Dict("N": Ref(15 0), ), "C": [0, 0, 0], )
Last updated on