annotationManager.exportAnnotCommand() removed parent annotation from xfdf output

WebViewer Version: 8.2.0

Do you have an issue with a specific file(s)? No
Can you reproduce using one of our samples or online demos? No
Are you using the WebViewer server? No
Does the issue only happen on certain browsers? No
Is your issue related to a front-end framework? No
Is your issue related to annotations? Yes

Please give a brief summary of your issue:

The annotationManager.exportAnnotCommand() function removed the xfdf of a parent annotation but the child annotations still remain, causing a null issue when using annotManager.getAnnotationById(annotation.InReplyTo)

Please describe your issue and provide steps to reproduce it:

I am unsure of how my user has done it, but through a series of actions in webviewer, he has made it so that the annotationManager.exportAnnotCommand() generated xfdf that did not include a parent annotation. Initially this is fine and I assumed the user meant to delete that annotation, but the problem is upon realizing that the parent annotation’s children wasn’t deleted. I assume they are children of the parent annotation because they have inreplyto fields that point to the deleted annotation’s name. I also assume that child annotations are also deleted when the parent is.

This caused an issue in other parts of our system because there I iterate through a given file’s annotations for display purposes and the code I use there assume that if an annotation has an inreplyto field, it points to a valid and existing annotation.

Please see below the firebase backup of one of our files that have this problem:
s32-prod-fb-Review-SPMMLS32-1246-18478-export-for-apryse.json (144.1 KB)

The Id/Name of the annotation that is missing is
06652318-7ed1-5445-71e9-40a44890cb71

Upon looking at our logs, I managed to find the latest xfdf of the parent annotation before and after it got overwritten:
parent-annotation-before.xml (8.3 KB)
parent-annotation-after.xml (1.9 KB)

You will see that in the “before” file, the annotation with Id
06652318-7ed1-5445-71e9-40a44890cb71
is there, while in the “after” file, it is gone. Meanwhile, the other annotations in the “after” file point to the now missing annotation and even after looking for it in the firebase backup, it cannot be found.

(The key is there but looking at the xfdf value of that key, none of the annotations there have a name value of 06652318-7ed1-5445-71e9-40a44890cb71)

The code below shows how we handle webviewer annotation events.
Our code is based on PDFTron’s realtime collaboration code that uses firebase.

 annotManager.addEventListener('annotationChanged', async (annotations, type, { imported }) => {

          if (imported)
            return;

          try {

            const xfdf = await annotManager.exportAnnotCommand();

            const processAnnotations = async () => {

              for (var i = 0; i < annotations.length; i++) {
                let annotation = annotations[i];

                if (type === 'add') {

                  let parentAuthorId = null;
                  if (annotation.InReplyTo) {
                    parentAuthorId = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
                  }

                  if (authorId) {
                    annotation.authorId = authorId;
                    annotation.setCustomData('userId', authorId);
                  }

                  let result = await server.createAnnotation(annotation.Id, {
                    authorId: authorId,
                    parentAuthorId: parentAuthorId,
                    xfdf: xfdf
                  });

               } else if (type === 'modify') {

                  let parentAuthorId = null;

                  if (!adminMode) {
                    if (authorId != annotation.authorId)
                      return;
                  }

                  if (annotation.InReplyTo) {
                    parentAuthorId = annotManager.getAnnotationById(annotation.InReplyTo).authorId || 'default';
                  }

                  let result = await server.updateAnnotation(annotation.Id, {
                    authorId: annotation.authorId,
                    parentAuthorId: parentAuthorId,
                    xfdf: xfdf
                  });

The code that causes an exception is in another part of the system but basically it calls
annotManager.getAnnotationById(annotation.InReplyTo)
and returns null, which I was not expecting to happen.

My questions are:
1.) Isn’t it supposed to be impossible for a parent annotation to not exist while there are still child annotations existing?
2.) Is this an issue on PDFTron webviewer? Or an issue on our side?

Please provide a link to a minimal sample where the issue is reproducible: N/A

Hi there,

Thank you for reaching out to WebViewer forums,

Deleting the parent annotation will delete the child/reply annotations. Could you please share how you were deleting the parent annotation that still kept the child annotations?

Could you please try updating to the latest version of WebViewer and see if you can reproduce the issue?
Are you able to reproduce this issue on our showcase? https://showcase.apryse.com/

Best regards,
Kevin Kim

Hi Kevin,

Upon further checking on our end, I have determine that the user was using the group annotations feature in webviewer.

The issue now is that If a user attempts to group two annotations together, say a free text annotation and a rectangle annotation, the resulting xfdf string will not contain the parent annotation.

Please see the steps below to replicate the issue:

1.) Create two markups and group them together

2.) Inspect the result of

annotManager.exportAnnotCommand();

The xfdf below is the actual output of exportAnnotCommand, you see that in this scenario, the rectangle annotation has disappeared.

<?xml version="1.0" encoding="UTF-8" ?>\n<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">\n<fields />\n<add />\n<modify><freetext page="0" rect="380.610,451.160,593.050,489.100" flags="print" inreplyto="3a123950-96ac-98d1-4ed1-a3038e78e639" replyType="group" name="17a1d1bb-c9fc-fd7a-8b1a-dfe69ef8b78e" title="Grace Wong, NBC" subject="Free Text" date="D:20230602164812+08'00'" width="0" creationdate="D:20230602163421+08'00'" TextColor="#E44234" FontSize="12"><trn-custom-data bytes="{&quot;trn-wrapped-text-lines&quot;:&quot;[\\&quot;test text 1 \\&quot;]&quot;}"/><contents>test text 1</contents><contents-richtext><body><p><span>test text 1</span></p></body></contents-richtext><defaultappearance>0 0 0 rg /Helvetica 12 Tf</defaultappearance><defaultstyle>font: Helvetica 12pt; text-align: left; text-vertical-align: top; color: #E44234</defaultstyle></freetext></modify>\n<delete />\n</xfdf>

I have tested this in both Webviewer 8.2.0 and 8.12.0

As an aside, is there a way to disable annotation grouping?

Hi there,

Thank you for your response,

Please see below observations:

If you add the following annotationChanged event handler:

const { annotationManager } = instance.Core;

    annotationManager.addEventListener('annotationChanged', (annotations, action) => {
      if (action === 'add') {
        console.log('this is a change that added annotations');
      } else if (action === 'modify') {
        console.log('this change modified annotations');
      } else if (action === 'delete') {
        console.log('there were annotations deleted');
      }

      annotations.forEach((annot) => {
        console.log('annotation page number', annot.PageNumber);
      });
    });

Then create a rectangle annotation then a free text annotation, calling exportAnnotationCommand will give you 2 annotations inside the ‘add’ element of the XFDF:

Afterwards, if I group the 2 annotations, then call exportAnnotationCommand, only one modified event is triggered and you will see that inside the XFDF, there is only one modified element, the freetext (second annotation):

This is why you are not seeing the parent annotation inside the XFDF. As you mentioned, this is the expected behaviour for annotation grouping.

We do not have a way to disable the annotation grouping, however you can simply block that button by hiding the element from the UI:
instance.UI.disableElement('annotationGroupButton')

Best regards,
Kevin Kim