> For the complete documentation index, see [llms.txt](https://docs.apryse.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.apryse.com/web/edit-page-content/text-edit.md).

# Edit Text and Images on PDF Pages Using the WebViewer JavaScript Library

Enhance PDF files with WebViewer's content edit feature. Edit text and images directly on PDFs. Enable this feature in the UI for easy editing. Try it out now! The Apryse Web SDK streamlines secure, s

WebViewer 10.3 adds support for WYSIWYG PDF content editing which allows you to edit text and images directly on PDF files.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-ecf44eaf2851d602b22f02e51c920d0f0e0db178%2F2153eba78d6c9fcd4186a23ca7eccf5bf17e27ad-2064x976.png?alt=media)

{% hint style="info" %}
Content editing is a new add-on that isn't part of existing license keys. To test it out you can comment out your license key and try in demo mode.
{% endhint %}

{% hint style="warning" %}
This feature only works with PDF files. If you want to edit Word documents you can try our [DOCX Editor](https://apryse.com/capabilities/docx-editor). For other type of files you can call the loadDocument function with the `loadAsPDF: true` option. For example `documentViewer.loadDocument('myfile.xlsx', { loadAsPDF: true })`
{% endhint %}

## Enable content editing feature with the WebViewer UI

To use the content editing feature with the WebViewer UI you need to enable the content edit feature in the UI.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  // options
}, document.getElementById('viewer'))
.then((instance) => {
  // Enable the tools to edit the PDF content
  instance.UI.enableFeatures([instance.UI.Feature.ContentEdit]);
});
```

{% endcode %}
{% endtab %}
{% endtabs %}

You will see the "Edit Text" toolbar group after the UI is loaded.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-34405202685eb109865f851c9d05402ed3871e2b%2Fef39d1643e78a6514fe0d24a83044a6ba2026eff-773x76.png?alt=media)

To proceed, click the "Edit Text" button or switch to content edit mode programmatically:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
instance.UI.setToolbarGroup(instance.UI.ToolbarGroup.EDIT_TEXT);
```

{% endcode %}
{% endtab %}
{% endtabs %}

The editable text and images in the document will be displayed with a dashed box around them and a side panel will be open with the text editing tools.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-881d20a214473425990bd52c190d1e226316a9c2%2Fc62afc9be9781572af9b7adc618882be0e67890a-2690x1206.png?alt=media)

## Editing content from the UI

### Text and Images

To edit the text you can either:

* Double click on the box

or

* Single click on the box to select it
* Click the edit button in the annotation popup menu

After this the text will become editable and once you've finished making changes you can click outside the box to have the text updated on the page.

Both text and images can be moved, resized and deleted. You can do this by selecting a box and then moving or resizing it like a normal annotation. You can also press the delete button to delete the content.

### Adding Paragraphs

To add a new paragraph, you can select the **Add Paragraph** tool button and then click and drag to create a rectangle that your new text will go inside. You can then start typing to add text and use the available tools in the right panel to adjust properties of the text.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-4049be9214820732b8214d5a899ab6b0dc610c3e%2Fdb635eb1a2fc4ccc0ed7dd4a31e4960c6190085e-1080x568.webp?alt=media)

### Adding Images

To add a new image, you can select the **Add Image** tool button and click on the document to choose where to place the image. The system file picker will open and you can select an image. After this, you can resize it, change its location or delete it.

## Using content editing with the WebViewer Core

If you use the WebViewer Core directly then there are some APIs provided to interact with content editing. As an example you can check out the [WebViewer Custom UI](https://github.com/ApryseSDK/webviewer-custom-ui/) Github repo which makes use of these APIs.

To begin editing content you'll have to call the method [startContentEditMode](https://sdk.apryse.com/api/web/Core.ContentEditManager.html#startContentEditMode__anchor).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const contentEditManager = Core.getDocumentViewer().getContentEditManager();
contentEditManager.startContentEditMode();
```

{% endcode %}
{% endtab %}
{% endtabs %}

To add new paragraphs, there is a tool called [AddParagraphTool](https://sdk.apryse.com/api/web/Core.Tools.AddParagraphTool.html) that you can switch into. For example:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const addParagraphTool = myDocumentViewer.getTool(Core.Tools.ToolNames.ADD_PARAGRAPH);
myDocumentViewer.setToolMode(addParagraphTool);
```

{% endcode %}
{% endtab %}
{% endtabs %}

You can also add images switching to the [AddImageContentTool](https://sdk.apryse.com/api/web/Core.Tools.AddImageContentTool.html).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const addImageContentTool = myDocumentViewer.getTool(Core.Tools.ToolNames.ADD_IMAGE_CONTENT);
myDocumentViewer.setToolMode(addImageContentTool);
```

{% endcode %}
{% endtab %}
{% endtabs %}

If you need to finalize the editing content mode, you can call the [endContentEditMode](https://sdk.apryse.com/api/web/Core.ContentEditManager.html#endContentEditMode__anchor) method.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const contentEditManager = Core.getDocumentViewer().getContentEditManager();
contentEditManager.endContentEditMode();
```

{% endcode %}
{% endtab %}
{% endtabs %}

The first time the tool mode is switched into the content edit mode WebAssembly files will be loaded. If you'd like to do this earlier you can use the [preloadWorker](https://sdk.apryse.com/api/web/Core.ContentEdit.html#.preloadWorker__anchor) method.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
Core.ContentEdit.preloadWorker(myDocumentViewer);
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Working with content edit placeholder annotations

You can tell if an annotation is a content edit placeholder annotation by using the [annotation.isContentEditPlaceHolder](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#isContentEditPlaceholder) API. With those annotations you can use the [getDocumentContent](https://sdk.apryse.com/api/web/Core.ContentEdit.html#.getDocumentContent) and [updateDocumentContent](https://sdk.apryse.com/api/web/Core.ContentEdit.html#.updateDocumentContent__anchor) APIs. For example:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// is the currently selected annotation a placeholder
const selectedAnnotation = annotationManager.getSelectedAnnotations()[0];
if (selectedAnnotation && selectedAnnotation.isContentEditPlaceholder()) {
  const content = await Core.ContentEdit.getDocumentContent(selectedAnnotation);

  // pass content to library that can display rich text, for example Quill
}


// later after the content has been updated this will update it on the page
await Core.ContentEdit.updateDocumentContent(annotation, newContent);
```

{% endcode %}
{% endtab %}
{% endtabs %}

If you want to check whether the annotation is for text or an image then you can use [getContentEditType](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#getContentEditType). For example:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
if (annotation.getContentEditType() === Core.ContentEditTypes.TEXT) {
  // this has text that can be updated
} else if (annotation.getContentEditType() === Core.ContentEditTypes.OBJECT) {
  // this has the image that can be updated
}
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Moving and deleting content

When the annotations representing the content are moved or deleted in the DocumentViewer then the content will automatically be updated on the page. You can also use the normal annotation APIs.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
annotationManager.deleteAnnotation(myContentEditAnnotation);

myOtherContentEditAnnotation.X = 50;
annotationManager.trigger(Core.AnnotationManager.Events.ANNOTATION_CHANGED, ['modify', [myOtherContentEditAnnotation], {}]);
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Setting the text style programatically

You have the ability to programmatically set the editor to apply a specific font style to the next characters. This style will remain active until it is updated by a subsequent selection or another call to the API.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const fontStyle = {
    "bold": false,
    "italic": false,
    "underline": true,
    "fontName": "Cousine",
    "fontSize": "12",
    "fontColor": "#FF00E8"
}
Core.ContentEdit.setTextAttributes(fontStyle);
```

{% endcode %}
{% endtab %}
{% endtabs %}

[ContentEdit#setTextAttributes](https://sdk.apryse.com/api/web/Core.ContentEdit.html)

By using the provided code snippet, you can effortlessly define the desired font style, such as bold, italic, and underline, along with the font name and size. The changes will be applied to the text within the editor, maintaining the style until further modifications are made through subsequent selections or API calls.

### Getting the text style from an active selection within a Content Edit Box

When you are editing text within a Content Edit Box, you have the ability to retrieve the current Text Attributes of your selection. This can be useful if you want to update your UI or confirm text properties as a user navigates the text.

First, attach a function to the [contentBoxEditStarted](https://sdk.apryse.com/api/web/Core.ContentEditManager.html) event that will store the reference to the active Content Box editor.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const handleEditorStarted = ({ editor }) => {
  // Store this editor reference for use later.
  // We will assume that reference is a variable 'contentEditorRef'.
  contentEditorRef = editor;
};
contentEditManager.addEventListener('contentBoxEditStarted', handleEditorStarted);
```

{% endcode %}
{% endtab %}
{% endtabs %}

[ContentEditManager#contentBoxEditStarted](https://sdk.apryse.com/api/web/Core.ContentEditManager.html)

Secondly, attach a function to the [contentEditSelectionChange](https://sdk.apryse.com/api/web/Core.ContentEditManager.html) event. This function should call the referenced editor we stored earlier to retrieve the Text Attributes with the [getTextAttributes](https://sdk.apryse.com/api/web/Core.ContentEdit.ContentBoxEditor.html) method. Once we obtain the attributes from the current selection, we can extract the various properties.

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const handleSelectionChange = async () => {
  if (contentEditorRef) {
    const attribute = await contentEditorRef.getTextAttributes();
    const { fontSize, fontName, fontColor, bold, italic, underline, textAlign } = attribute;
  }
};
contentEditManager.addEventListener('contentEditSelectionChange', handleSelectionChange);
```

{% endcode %}
{% endtab %}
{% endtabs %}

[ContentEditManager#contentEditSelectionChange](https://sdk.apryse.com/api/web/Core.ContentEditManager.html) [ContentBoxEditor#getTextAttributes](https://sdk.apryse.com/api/web/Core.ContentEdit.ContentBoxEditor.html)

### Getting the text style from a selected Content Edit Box

You can extract the Text Style (e.g., font size, color) from a Content Edit Box that isn't in edit mode. This process fetches the first instance of each property. For instance, if a box has both 12-point and 30-point fonts, with the 12-point appearing first, the output will be 12 points.

To do this:

* Select an Annotation
* Obtain the `contentEditBoxId`
* Request the Content Box Attributes from the Content Edit Manager

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
const contentBoxId = annotation.getCustomData('contentEditBoxId');
const contentEditManager = Core.getDocumentViewer().getContentEditManager();
const textAttributes = await contentEditManager.getContentBoxAttributes(contentBoxId);
```

{% endcode %}
{% endtab %}
{% endtabs %}

[ContentEditManager#getContentBoxAttributes](https://sdk.apryse.com/api/web/Core.ContentEditManager.html) [Annotation#getCustomData](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html)

### Undo / Redo changes programmatically

You have the capability to programmatically undo or redo changes made during PDF text edits. This functionality encompasses actions such as character additions/deletions, style modifications, and adjustments to content boxes. These changes are managed through the [Content Edit History Manager](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html).

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
// to undo a change
await Core.getDocumentViewer().getContentEditHistoryManager().undo();
// to redo a change
await Core.getDocumentViewer().getContentEditHistoryManager().redo();
// to check if there is an available change to undo
const canUndo = Core.getDocumentViewer().getContentEditHistoryManager().canUndo();
// to check if there is an availabe change to redo
const canRedo = Core.getDocumentViewer().getContentEditHistoryManager().canRedo();
```

{% endcode %}
{% endtab %}
{% endtabs %}

[ContentEditHistoryManager#undo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) [ContentEditHistoryManager#redo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) [ContentEditHistoryManager#canUndo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) [ContentEditHistoryManager#canRedo](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html)

Using the provided code snippet, you can easily initiate undo and redo operations programmatically. The [undo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) function allows you to reverse the most recent change, while the [redo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) function re-applies a previously undone change. Additionally, you can utilize the [canUndo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) and [canRedo()](https://sdk.apryse.com/api/web/Core.ContentEditHistoryManager.html) functions to check if there are available changes to be undone or redone, respectively. This grants you control over the revision history and enables a seamless editing experience.

## Customized Deletion of Link Annotations through API

Starting with WebViewer 10.7, we've introduced an enhanced feature that displays a link annotation popup. This popup includes link information and an unlink button, which can be triggered simply by hovering over any link annotations within a document.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-2288ba7882f22d73d63f71c51b5197d52a2b8000%2Fb058a10ffd8df14fb1c70b3090294c1bfee74982-2844x1844.gif?alt=media)

Currently, there is no direct API to remove a hyperlink that has been added in Content Edit Mode. However, an alternative approach is available, allowing you to detect the link annotation you've added, store it, and then perform the deletion action.

### Detect link annotation

Utilize the [mouseMove](https://sdk.apryse.com/api/web/Core.DocumentViewer.html#event:mouseMove__anchor/) event in conjunction with the available API [getAnnotationsByMouseEvent](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getAnnotationsByMouseEvent__anchor) to identify link annotations.

Here is the example of an onHover for annotations:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  initialDoc: 'https://myserver.com/myfile.pdf'
}, document.getElementById('viewer'))
  .then(instance => {
    const { documentViewer, annotationManager } = instance.Core;

    documentViewer.addEventListener('mouseMove', evt => {
      const annotations = Core.getAnnotationManager().getAnnotationsByMouseEvent(evt, true);
      const linkAnnot = annotations.find((annot) => annot instanceof window.Core.Annotations.Link);
    });
  });
```

{% endcode %}
{% endtab %}
{% endtabs %}

[AnnotationManager.getAnnotationsByMouseEvent](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getAnnotationsByMouseEvent)

### Delete link annotation

The annotations include links from within WebViewer, both in and outside of Content Edit mode, or from embedded URLs in the document Regardless of the link annotations obtained, you can use the [deleteAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#deleteAnnotations__anchor/) method to remove them from the document. Please note that, in the current implementation, the link style will still persist.

Here's an example of removing link annotations generated by WebViewer:

{% tabs %}
{% tab title="JavaScript" %}
{% code lineNumbers="true" %}

```js
WebViewer({
  initialDoc: 'https://myserver.com/myfile.pdf'
}, document.getElementById('viewer'))
  .then(instance => {
    const { annotationManager } = instance.Core;
    const linkAnnotations = annotationManager.getAnnotationsList().filter((annot) => annot instanceof window.Core.Annotations.Link);
    // add {'source': 'unlink'} to let content edit worker know to refresh cached data
    annotationManager.deleteAnnotations(linkAnnotations, { 'source': 'unlink' });
  });
```

{% endcode %}
{% endtab %}
{% endtabs %}

[WebViewerInstance](https://sdk.apryse.com/api/web/WebViewerInstance.html) [AnnotationManager.getAnnotationsList](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getAnnotationsList__anchor) [AnnotationManager.deleteAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#deleteAnnotations__anchor) [AnnotationManager.getGroupAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#getGroupAnnotations__anchor) [AnnotationManager.ungroupAnnotations](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#ungroupAnnotations__anchor)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.apryse.com/web/edit-page-content/text-edit.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
