Introduction to Annotations

Annotations

PSPDFKit supports most common annotation types:

These are standard annotations (as defined in the PDF Reference) that can be read and written by many apps like Adobe Acrobat or even Apple's Preview.app.

Working with Annotations in Code

After loading a document you can access its annotations via the AnnotationProvider returned by PdfDocument#getAnnotationProvider. The annotation provider supports reading, adding and removing of annotations to and from the document.

1
2
val pageIndex = 0
val annotations: List<Annotation> = document.annotationsProvider.getAnnotations(pageIndex)
1
2
final int pageIndex = 0;
final List<Annotation> annotations = document.getAnnotationsProvider().getAnnotations(pageIndex);

Here is another example of how to read all NoteAnnotation objects from a document, using the async AnnotationProvider#getAllAnnotationsOfType method.

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
// Get the observable emitting all annotatiosn of type NOTE.
val annotationObservable = document.annotationProvider
    .getAllAnnotationsOfType(EnumSet.of(AnnotationType.NOTE))

// This will asynchronously read all annotations, cast them and return them as a List.
annotationObservable
    .cast(NoteAnnotation::class.java)
    .toList() // Collect all annotations into a List.
    .observeOn(AndroidSchedulers.mainThread()) // Receive all annotations on the main thread.
    .subscribe { noteAnnotations ->
        // This is called on the main thread.
        doSomething(noteAnnotations)
    }
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Get the observable emitting all annotatiosn of type NOTE.
final Observable<Annotation> annotationObservable = document
    .getAnnotationProvider()
    .getAllAnnotationsOfType(EnumSet.of(AnnotationType.NOTE));

// This will asynchronously read all annotations, cast them and return them as a List.
annotationObservable
    .cast(NoteAnnotation.class)
    .toList() // Collect all annotations into a List.
    .observeOn(AndroidSchedulers.mainThread()) // Receive all annotations on the main thread.
    .subscribe(new Consumer<List<NoteAnnotation>>() {
        @Override public void accept(List<NoteAnnotation> noteAnnotations) {
            // This is called on the main thread.
            doSomething(noteAnnotations);
        }
    });

Creating Annotations

You can create new annotations using their public constructor passing in required annotation properties.

Copy
1
2
3
4
5
// Create a FreeTextAnnotation
val contents = "Add text to pages using FreeTextAnnotations"
val pageIndex = 0
val pageRect = RectF(500f, 830f, 720f, 780f)
val freeTextAnnotation = FreeTextAnnotation(pageIndex, pageRect, contents)
Copy
1
2
3
4
5
// Create a FreeTextAnnotation
final String contents = "Add text to pages using FreeTextAnnotations";
final int pageIndex = 0;
final RectF pageRect = new RectF(500f, 830f, 720f, 780f);
final FreeTextAnnotation freeTextAnnotation = new FreeTextAnnotation(pageIndex, pageRect, contents);

Each annotation subtype derives from Annotation which provides the basic set of properties that is common to all annotations, like Annotation#getBoundingBox. For a list of properties that are specific to a particular annotation subtype, consult the annotation API reference of that annotation class.

Adding and Removing Annotations

After creating an instance of an annotation it is not attached to any document. To add the annotation to the page of a document you can call AnnotationProvider#addAnnotationToPage on the annotation provider of the specific document. If you are displaying the modified document using the PdfActivity or PdfFragment you need to notify those components to update the UI by calling PdfFragment#notifyAnnotationHasChanged after any modification to the annotations.

Copy
1
2
3
4
5
6
7
8
9
// Add the annotation to the document (document is a PdfDocument).
document.annotationProvider.addAnnotationToPage(freeTextAnnotation)
// Tell PSPDFKit to update the annotation visually (fragment is a PdfFragment).
// You can get the fragment by accessing 'PdfFragment' on the PdfActivity.
fragment.notifyAnnotationHasChanged(freeTextAnnotation)

// Use this to remove an annotation.
document.annotationProvider.removeAnnotationFromPage(someOtherAnnotation)
fragment.notifyAnnotationHasChanged(someOtherAnnotation)
Copy
1
2
3
4
5
6
7
8
9
// Add the annotation to the document (document is a PdfDocument).
document.getAnnotationProvider().addAnnotationToPage(freeTextAnnotation);
// Tell PSPDFKit to update the annotation visually (fragment is a PdfFragment).
// You can get the fragment by calling getPdfFragment() on PdfActivity.
fragment.notifyAnnotationHasChanged(freeTextAnnotation);

// Use this to remove an annotation.
document.getAnnotationProvider().removeAnnotationFromPage(someOtherAnnotation);
fragment.notifyAnnotationHasChanged(someOtherAnnotation);

Example: Have a look at the AnnotationCreationExample inside the catalog app, which shows how to create annotations for various other annotation types as well.

Annotation Callbacks

The PdfFragment implements the AnnotationManager interface, allowing to register listeners that are notified whenever an annotation is selected, modified, or deselected.

Copy
MyActivity.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
override fun onCreate(savedInstanceState : Bundle?) {
    super.onCreate(savedInstanceState)

    pdfFragment.addOnAnnotationSelectedListener(object :AnnotationManager.OnAnnotationSelectedListener {
        override fun onPrepareAnnotationSelection(controller: AnnotationSelectionController, annotation: Annotation, annotationCreated: Boolean): Boolean {
            // Returning false here would prevent the annotation from being selected.
            return true
        }

        override fun onAnnotationSelected(annotation: Annotation, annotationCreated: Boolean) {
            Log.i(TAG, "The annotation was selected.")
        }
    })

    pdfFragment.addOnAnnotationDeselectedListener { annotation, reselected ->
        Log.i(TAG, "The annotation was deselected.")
    }

    pdfFragment.addOnAnnotationUpdatedListener(object: OnAnnotationUpdatedListener {
        override fun onAnnotationCreated(annotation: Annotation) {
            Log.i(TAG, "The annotation was created.")
        }

        override fun onAnnotationUpdated(annotation: Annotation) {
            Log.i(TAG, "The annotation was updated.")
        }

        override fun onAnnotationRemoved(annotation: Annotation) {
            Log.i(TAG, "The annotation was removed.")
        }
    })

    // This will remove all previously registered listeners. Instead you could unregister them selectively.
    pdfFragment.clearAnnotationListeners()
}
Copy
MyActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    getPdfFragment().addOnAnnotationSelectedListener(new AnnotationManager.OnAnnotationSelectedListener() {
        @Override
        public boolean onPrepareAnnotationSelection(@NonNull AnnotationSelectionController controller, @NonNull Annotation annotation, boolean annotationCreated) {
            // Returning false here would prevent the annotation from being selected.
            return true;
        }

        @Override public void onAnnotationSelected(@NonNull Annotation annotation, boolean annotationCreated) {
            Log.i(TAG, "The annotation was selected");
        }
    });

    getPdfFragment().addOnAnnotationDeselectedListener(new AnnotationManager.OnAnnotationDeselectedListener() {
        @Override public void onAnnotationDeselected(@NonNull Annotation annotation, boolean reselected) {
            Log.i(TAG, "The annotation was deselected");
        }
    });

    getPdfFragment().addOnAnnotationUpdatedListener(new AnnotationProvider.OnAnnotationUpdatedListener() {
        @Override
        public void onAnnotationCreated(@NonNull Annotation annotation) {
            Log.i(TAG, "The annotation was created.");
        }

        @Override
        public void onAnnotationUpdated(@NonNull Annotation annotation) {
            Log.i(TAG, "The annotation was updated.");
        }

        @Override
        public void onAnnotationRemoved(@NonNull Annotation annotation) {
            Log.i(TAG, "The annotation was removed.");     
        }
    });

    // This will remove all previously registered listeners. Instead you could unregister them selectively.
    getPdfFragment().clearAnnotationListeners();
}

Annotation Saving

By default PSPDFKit auto-saves changes to a document and to annotations inside PdfFragment#onStop – effectively that is every time the fragment is sent to the background, e.g. when switching to another application, or leaving the viewer activity. You can disable auto-saving via the #autosaveEnabled setter on the PdfConfiguration.Builder.

Copy
1
2
3
4
5
6
7
// By default auto-save is enabled.
val config = PdfConfiguration.Builder()
    .autosaveEnabled(false)
    .build()

val fragment = PdfFragment.newInstance(documentUri, config)
...
Copy
1
2
3
4
5
6
7
// By default auto-save is enabled.
final PdfConfiguration config = new PdfConfiguration.Builder()
    .autosaveEnabled(false)
    .build();

final PdfFragment fragment = PdfFragment.newInstance(documentUri, config);
...

If you are using the PdfActivity, you can also deactivate auto-save via the #autosaveEnabled setter of the PdfActivityConfiguration.Builder.

Copy
1
2
3
4
5
6
7
// By default auto-save is enabled.
val config = PdfActivityConfiguration.Builder(context)
    .autosaveEnabled(false)
    .build()

PdfActivity.showDocument(context, documentUri, config)
...
Copy
1
2
3
4
5
6
7
8
// By default auto-save is enabled.
final PdfActivityConfiguration config =
    new PdfActivityConfiguration.Builder(context)
        .autosaveEnabled(false)
        .build();

PdfActivity.showDocument(context, documentUri, config);
...

If you're interested in manually saving annotations, check out our annotation saving guide.