Document Editing

The Document Editor is a separate component of PSPDFKit for Web, and it’s available for licenses with the document editing feature. It provides you and your users with a whole host of page editing features, including new page creation, page duplication, reordering, merging imported documents, rotation, and deletion. The resulting document can be saved or exported.

This guide describes how you can use the document editing API. To learn more about the UI, see the Document Editing UI article.

Document Editor UI

The Document Editor’s UI is integrated into PSPDFKit.ViewState.InteractionMode as a separate interaction mode. To switch to the document editing view, you can simply change the view mode to PSPDFKit.ViewState.InteractionMode.DOCUMENT_EDITOR:

1
2
3
4
5
6
instance.setViewState(viewState =>
  viewState.set(
    "interactionMode",
    PSPDFKit.ViewState.InteractionMode.DOCUMENT_EDITOR
  )
);

If your license has the document editing feature, PSPDFKit.defaultToolbarItems includes a PSPDFKit.ToolbarItem, which can be used to facilitate switching to document editing mode via the user interface. This button is added to the main toolbar.

You can remove it from the UI by setting the corresponding PSPDFKit.Configuration.toolbarItems value:

1
2
3
4
5
PSPDFKit.load({
  toolbarItems: PSPDFKit.defaultToolbarItems.filter(
    toolbarItem => toolbarItem.type !== "document-editor"
  )
});

Programmatic Access

In addition to a comprehensive UI, the Document Editor also comes with convenient API methods that you can use to perform document editing in code.

PSPDFKit.Instance#applyOperations(operations)

Calling this API method with an array of PSPDFKit.DocumentOperation as its argument will perform the provided operations on the current document. Once the operations have been performed, the current document will be reloaded and updated.

All the provided operations will be performed in a single call to instance.applyOperations(). After applying each operation in the order it comes in in the provided array, a temporary document will result from it; the next operation in the array will be applied on this temporary document, and not on the original one. This means that the page indexes referenced in an operation should take into account any changes in the document that have been performed by previous operations in the array.

When using PSPDFKit Instant, if a client performs modifications in a shared document instance, other clients will lose access to the document and will be prompted to update their local views. If they don’t reload the document, they won’t be able to persist any further changes they make to it until they reload the browser.

These are the data types used by the Document Editor API:

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
type Rotation = 0 | 90 | 180 | 270;

type AddPageConfiguration = {
  backgroundColor: PSPDFKit.Color,
  pageWidth: number,
  pageHeight: number,
  rotateBy: Rotation,
  insets?: PSPDFKit.Geometry.Rect
};

type DocumentOperation =
  | { type: "addPage", afterPageIndex: number, ...AddPageConfiguration }
  | { type: "addPage", beforePageIndex: number, ...AddPageConfiguration }
  | { type: "duplicatePages", pageIndexes: Array<number> }
  | { type: "movePages", pageIndexes: Array<number>, afterPageIndex: number }
  | { type: "movePages", pageIndexes: Array<number>, beforePageIndex: number }
  | { type: "rotatePages", pageIndexes: Array<number>, rotateBy: Rotation }
  | { type: "keepPages", pageIndexes: Array<number> }
  | { type: "removePages", pageIndexes: Array<number> }
  | {
      type: "importDocument",
      afterPageIndex: number,
      treatImportedDocumentAsOnePage: boolean,
      document: File | Blob
    }
  | {
      type: "importDocument",
      beforePageIndex: number,
      treatImportedDocumentAsOnePage: boolean,
      document: File | Blob
    };

PSPDFKit.Instance#exportPDFWithOperations(operations)

Calling this API method with an array of PSPDFKit.DocumentOperation as its argument will create a modified copy of the current document on which the provided operations have been performed. Using this API method will not affect the current document.

The method will resolve with an ArrayBuffer that can be used, for example, to save the resulting document:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
async () => {
  const buffer = await instance.exportPDFWithOperations([
    {
      type: "rotatePages",
      pageIndexes: [0],
      rotateBy: 270
    }
  ]);
  const blob = new Blob([buffer], { type: "application/pdf" });
  if (navigator.msSaveOrOpenBlob) {
    navigator.msSaveOrOpenBlob(blob, "download.pdf");
  } else {
    let a = document.createElement("a");
    const objectUrl = window.URL.createObjectURL(blob);
    a.href = objectUrl;
    a.style = "display: none";
    a.download = "download.pdf";
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(objectUrl);
  }
};

Operations

What follows is a list of the operations that are available in the Document Editor API. All the examples use the instance.applyOperations() method, but they also apply to instance.exportPDFWithOperations().

addPage

Adds a blank page before or after the specified page index using the provided configuration:

Copy
1
2
3
4
5
6
7
8
9
10
11
instance.applyOperations([
  {
    type: "addPage",
    afterPageIndex: 1, // Add new page after page 1.
    backgroundColor: new PSPDFKit.Color({ r: 100, g: 200, b: 255 }), // Set the new page background color.
    pageWidth: 750,
    pageHeight: 1000,
    rotateBy: 0 // No rotation.
    // Insets are optional.
  }
]);

keepPages

Removes all the pages from the document except for the pages specified in the pageIndexes array:

1
2
3
4
5
6
instance.applyOperations([
  {
    type: "keepPages",
    pageIndexes: [0, 1, 2] // Remove all pages except pages 0 to 2.
  }
]);

duplicatePages

Duplicates the pages specified in the pageIndexes array. Each duplicated page will be inserted after the page being duplicated:

Copy
1
2
3
4
5
6
instance.applyOperations([
  {
    type: "duplicatePages",
    pageIndexes: [0, 4] // Duplicate pages 0 and 4, and insert each duplicate after the original page.
  }
]);

movePages

Moves the pages specified in the pageIndexes array before or after the specified page index:

Copy
1
2
3
4
5
6
7
instance.applyOperations([
  {
    type: "movePages",
    pageIndexes: [0, 4], // Move pages 0 and 4.
    beforePageIndex: 7 // The specified pages will be moved after page 7.
  }
]);

rotatePages

Rotates the pages specified in the pageIndexes array by the degrees indicated in rotateBy. Only multiples of 90 under 360 are allowed as values:

Copy
1
2
3
4
5
6
7
instance.applyOperations([
  {
    type: "rotatePages",
    pageIndexes: [0], // Rotate page 0.
    rotateBy: 90 // Rotate page 90 degrees clockwise.
  }
]);

removePages

Removes the pages specified in the pageIndexes array:

1
2
3
4
5
6
instance.applyOperations([
  {
    type: "removePages",
    pageIndexes: [8, 9, 11] // Remove pages 8, 9, and 11.
  }
]);

importDocument

Imports the provided document before or after the specified page index.

If the same document’s Blob object is used in the same operations array for different operations, the same Blob object will be reused for all of them:

Copy
1
2
3
4
5
6
7
8
instance.applyOperations([
  {
    type: "importDocument",
    afterPageIndex: [10], // Import document after page 10.
    treatImportedDocumentAsOnePage: false, // All the imported document pages will appear individually in the Document Editor UI.
    document: blob // Document to import.
  }
]);

Here is an example that uses fetch to retrieve an external document, converts it to a Blob, and passes it in an importDocument operation:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
fetch("imported.pdf")
  .then(res => {
    if (!res.ok) {
      throw res;
    }
    return res;
  })
  .then(res => res.blob())
  .then(blob => {
    instance.applyOperations([
      {
        type: "importDocument",
        beforePageIndex: 3,
        document: blob,
        treatImportedDocumentAsOnePage: false
      }
    ]);
  });

Multiple Operations

Operations can be chained in a single call to instance.applyOperations() or instance.exportPDFWithOperations(). When performing multiple operations, always keep in mind that the page indices in each operation must reference the corresponding page indices that result from performing the previous operation.

In order to better understand the implications of this behavior, let’s see a failing example. We’ll assume the current document has six pages, which are indexed from 0 to 5:

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
instance.applyOperations([
  // First, we remove the last two pages from the document.
  {
    type: "removePages",
    pageIndexes: [4, 5] // Remove pages 4 and 5.
  },
  // The resulting document would have four pages, indexed from `0` to `3`.
  // The next operation will throw an error, as there is no longer a page with index 5.
  {
    type: "rotatePages",
    pageIndexes: [5], // Rotate page 5.
    rotateBy: 90 // Rotate page 90 degrees clockwise.
  }
]);

On the other hand, this next example would succeed in spite of operating on a page that does not exist in the original document (we’ll assume we have a six-page document again):

Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
instance.applyOperations([
  // First, we add a page at the beginning of the document.
  {
    type: "addPage",
    beforePageIndex: 0, // Add new page after page 1.
    backgroundColor: new PSPDFKit.Color({ r: 255, g: 255, b: 255 }), // Set white as the new page background color.
    pageWidth: 750,
    pageHeight: 1000,
    rotateBy: 0 // No rotation.
    // Insets are optional.
  },
  // The resulting document would have 7 pages now, indexed from `0` to `6`.
  // The next operation will succeed, as page 6 now exists.
  {
    type: "rotatePages",
    pageIndexes: [6], // Rotate page 6.
    rotateBy: 90 // Rotate page 90 degrees clockwise.
  }
]);