RELEASE: What's New in Summer 2024

Taking Control of Annotations in WebViewer

By Roger Dunham | 2024 Aug 08

Sanity Image
Read time

4 min

Summary: This article discusses PDF annotations and how they can be managed in Apryse WebViewer. It explains how to control user permissions for viewing, editing, or deleting annotations using the setPermissionCheckCallback function on AnnotationManager, allowing fine-tuned access based on user roles.

Introduction

Copied to clipboard

We all know that PDFs are a great and portable file format that is widely understood. But what about annotations within them?

Annotations come in many forms, such as text, graphics, highlights, text boxes, and redaction marks, to name just a few. Technically, annotations are separate from the PDF content and can even be exported separately into a database, allowing version control to be implemented, making them even more awesome.

Want an in-depth look at annotations? Check out our Comprehensive Guide to PDF Annotations with JavaScript.

By default, users can only modify the annotations they create, but you can customize this. The permissions include:

  • Admin: Fully allowed to create/reply to/edit/delete any annotations
  • User: Allowed to create/reply to any annotations but only allowed to edit/delete their own annotations
  • Read Only: Not allowed to create/reply to/edit/delete any annotations.

In the recent article about Role Based Access Control, we saw how we can use those options to set up a system within Apryse WebViewer to control which annotations can be viewed, edited or deleted by specific users.

In this article, we will explore how to fine-tune user permissions by using the setPermissionCheckCallback function on AnnotationManager.

What is setPermissionCheckCallback?

Copied to clipboard

This function, if present, determines whether a specific annotation is editable. It should define a callback that returns true if the annotation can be edited or false if it cannot.

What’s more, if the function is present, then it overrides the permissions that would be used for an Admin or User (as defined above). It does not, however, override the AnnotationManager read-only mode.

Having access to a callback that works in this way opens the possibility of solving a whole range of use-cases that require sophisticated handling.

For example, you can have:

  • Specific annotations that cannot be deleted by Admins,
  • Annotations that can be deleted by anyone, even when created by another user
  • Annotations that are editable only if created in the last week
  • Annotations that can be edited if created by one author but not if created by another author
  • Annotations that can be deleted only if they contain specific text

In fact, if you wanted, then you could use the system to prevent annotations from being modified unless they were created on a Monday by an author called “Jeremy,” which is located on the right-hand side of page two.

I have no idea why you would want to do that, but the flexibility to do so is one reason why the Apryse SDK is a preferred choice for many developers – and by the end of this article, you will see how it can be done.

Setting Default Behavior

Copied to clipboard

Since this callback will be used every time that WebViewer needs to know what the permissions should be for an annotation, we need to implement the default behavior. We can then extend the code to include our custom behavior. As a result, typical code will look something like this:

annotationManager.setPermissionCheckCallback((author, annotation) => { 
// the default permission check that is used 
    const defaultPermission = annotation.Author === annotationManager.getCurrentUser() || annotationManager.isUserAdmin(); 
// Specify a custom check 
    const customPermission = [add code logic here] 
// You can return the customPermission as it is, or else return the least strict permission 
    return defaultPermission || customPermission; 
   }); 

The exact implementation of the code is up to you, of course, and can be tailored to suit your requirements. Let’s look at a few examples.

Allow all users to delete squiggly underlines

Copied to clipboard

Some people like squiggly underlines, other people don’t. Let’s create a system where any user can delete squiggly underlines, irrespective of who created them. 

Normally, this would not be allowed since the out-of-the-box behavior is that annotations can only be deleted by the person who created them or by an Admin.

In order to implement customer permission, we need to know if the selected annotation, passed to the callback, is of the Core.Annotations.TextSquigglyAnnotation type.

A gotcha here: If you have come from a .NET background (like I did), it is that, as in TypeScript, it is a Type Assertion, not a true cast.  This means that annotation, as TextSquigglyAnnotation, will result in an object (assuming annotation is non-null) even if the annotation is not of the specified type.

If you want to know whether the annotation is a particular type, then use an instance instead.

If the annotation is a squiggly, then it should be editable so we can return true. If not, then the default value should be returned (since we also need to handle when the annotation type is, for example, free text).

You can implement this with the following code.

annotationManager.setPermissionCheckCallback((author, annotation) => { 
    // the default permission check that is used 
    // you can combine this with your own custom checks 
    const defaultPermission = annotation.Author === annotationManager.getCurrentUser() || annotationManager.isUserAdmin(); 
    const isSquiggly = annotation instanceof Core.Annotations.TextSquigglyAnnotation 
    const customPermission = isSquiggly 
    return defaultPermission || customPermission; 
   }); 

Let’s see what that looks like. Let’s open a PDF with squiggly underlines added by another author. By default, we won’t be able to delete one created by Justin.

Blog image

Figure 1 - By default a user cannot delete an annotation unless they created it.

However, if we use our custom code then we can edit, add links to, or delete the annotation.

Blog image

Figure 2 - Using setPermissionCheckCallback means that you can permit any user to delete squiggly underlines.

Allow comments that start with the word “Review” to be edited and deleted

Copied to clipboard

OK, let’s look at another example. We have a PDF, and many users can add comments via sticky notes.

We want to implement a logic so that if the text within a comment begins with ‘Review’ then the annotation is editable by any user. If the text starts differently, then the default rules should apply, with only the author or an administrator being able to edit or delete the annotation.

Just as before we can use instanceof to create custom logic that only relates to sticky notes.

If one is found, then we get the text from the annotation using getContents().

Finally, we check whether the text starts with ‘Review’ (allowing for case differences). If so then the annotation is editable, and if not, then the editability is controlled by the default mechanism.

   annotationManager.setPermissionCheckCallback((author, annotation) => { 
    // the default permission check that is used 
    // you can combine this with your own custom checks 
     const defaultPermission = annotation.Author === annotationManager.getCurrentUser() || annotationManager.isUserAdmin(); 
    const isSticky = annotation instanceof Core.Annotations.StickyAnnotation 
    if (isSticky) { 
     const contents = annotation.getContents() 
     const customPermission = (contents?.toLowerCase()?.startsWith("review:")) 
     return defaultPermission || customPermission; 
    } 
    return defaultPermission; 
   }); 

In this example I have chosen to return from multiple locations in the code, but if you prefer the coding style of just a single return then the code can easily be modified to do so.

Let’s see what happens when we use that code when logged in as “Guest”, and try to edit a sticky note that was created by Sally.

Blog image

Figure 3 - Attempting to add an annotation that was created by a different user is not allowed by default.

Since the comment text does not start with ‘Review:’ the annotation is read-only (you can tell that by the red outline).

On the other hand, if the text starts with ‘Review:’ then the custom permissions are used, and the annotation can be modified or deleted (and the outline is blue).

Blog image

Figure 4 - The text in the comment has been used to identify that the annotation should be given custom permissions, even though it was created by a different user.

Next steps

Copied to clipboard

While we have used simple examples, the mechanism can be made as sophisticated as you require. There are many other properties that you can use to control the permissions logic. For example:

Put those together and you can create custom permissions even for gnarly business rules, such as the one that I mentioned at the start of the article.

Awesome!

There is a whole world of functionality available, so check out the documentation for annotations to dive in.

If you run into any problems, please reach out to us on Discord and our helpful Solution Engineers will be happy to help.

 

Sanity Image

Roger Dunham

Share this post

email
linkedIn
twitter