Preserve attachments used for stamp annotation templates

Q: How can I avoid losing the image attachments of image annotations I created for stamp annotation templates after document operations are applied?

A: When document operations are applied, either by calling PSPDFKit.Instance#applyOperations or using the Document Editor UI, the current instance is reloaded to reflect the new version of the document. In that process, image attachments that aren’t currently used anywhere on the document are destroyed. Thus, if you had created some image annotations and added those as stamp annotation templates, their image attachments will no longer appear.

In order to avoid this, you can keep a cached copy of the image attachments in memory and re-create the image attachments once the document has changed. Here is an example where we store the image attachments used on image annotations added for stamp annotation templates whenenver the current interaction mode changes to PSPDFKit.InteractionMode#DOCUMENT_EDITOR and restore that copy on document.change events:

Copy
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
let instance = null;

// Object that will contain our attachments
let attachmentsCache = null;

PSPDFKit.load({
    // your configuration
  }).then(async _instance => {
    instance = _instance;
    const filePaths = ["a.png", "b.png"] // path to your images
    const imageBlobs = await Promise.all(
      filePaths.map(name => fetch(`/assets/${name}`).then(response => response.blob()))
    );
    const attachments = await Promise.all(
      imageBlobs.map(file => instance.createAttachment(file))
    );
    const annotations = attachments.map(
      imageAttachmentId =>
        new PSPDFKit.Annotations.ImageAnnotation({
          imageAttachmentId,
          contentType: "image/png",
          boundingBox: new PSPDFKit.Geometry.Rect({
            width: 300,
            height: 200,
            top: 0,
            left: 0
          })
        })
    );

    // We add the image annotations created as stamp annotation templates 
    instance.setStampAnnotationTemplates(stampAnnotationTemplates => [
      ...annotations,
      ...stampAnnotationTemplates
    ]);

    instance.addEventListener("viewState.change", state => {
      if (
        state.interactionMode === PSPDFKit.InteractionMode.DOCUMENT_EDITOR
      ) {
        // As soon as the document editor is opened we cache the
        // current attachments used on stamp annotation templates
        const attachmentIds = instance.stampAnnotationTemplates
          .filter(annotation => annotation.imageAttachmentId)
          .map(annotation => annotation.imageAttachmentId);
        // from the image attachment id we need to obtain the underlying
        // Blob
        attachmentsCache = Promise.all(
          attachmentIds.map(id => instance.getAttachment(id))
        );
      }
    });
    instance.addEventListener("document.change", async () => {
      // "document.change" events are invoked after operations are
      // applied to the current instance
      if (!attachmentsCache) {
        return;
      }
      const attachments = await attachmentsCache;
      await Promise.all(
        attachments.map(attachment => instance.createAttachment(attachment))
      );
      // All attachments were re-created at this point
    });
});

If document operations are applied programmatically using PSPDFKit.Instance#applyOperations you can in turn cache the current image attachments just before calling that method and re-create the attachments once the Promise returned by the former method resolves:

Copy
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
let instance = null;

// Object that will contain our attachments
let attachmentsCache = null;

PSPDFKit.load({
    // your configuration
  }).then(async _instance => {
    instance = _instance;
    const filePaths = ["a.png", "b.png"] // path to your images
    const imageBlobs = await Promise.all(
      filePaths.map(name => fetch(`/assets/${name}`).then(response => response.blob()))
    )
    const attachments = await Promise.all(
      imageBlobs.map(file => instance.createAttachment(file))
    );
    const annotations = attachments.map(
      imageAttachmentId =>
        new PSPDFKit.Annotations.ImageAnnotation({
          imageAttachmentId,
          contentType: "image/png",
          boundingBox: new PSPDFKit.Geometry.Rect({
            width: 300,
            height: 200,
            top: 0,
            left: 0
          })
        })
    );

    // We add the image annotations created as stamp annotation templates
    instance.setStampAnnotationTemplates(stampAnnotationTemplates => [
      ...annotations,
      ...stampAnnotationTemplates
    ]);
    const attachmentIds = instance.stampAnnotationTemplates
      .filter(annotation => annotation.imageAttachmentId)
      .map(annotation => annotation.imageAttachmentId);
    // from the image attachment id we need to obtain the underlying
    // Blob
    attachmentsCache = await Promise.all(
      attachmentIds.map(id => instance.getAttachment(id))
    );

    const additionalPDF = await fetch("assets/example.pdf").then(response =>
      response.blob()
    );

    // Manually apply a "importDocument" operation
    await instance.applyOperations([
      {
        type: "importDocument",
        afterPageIndex: instance.totalPageCount - 1,
        document: additionalPDF
      }
    ]);

    await Promise.all(
      attachmentsCache.map(attachment => instance.createAttachment(attachment))
    );

    // All attachments were re-created at this point
});

This has been tested with PSPDFKit for Web 2019.5.4.