> 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/annotation/customize/creating-custom-annotations.md).

# Custom Annotations in WebViewer

Learn how to create and customize non-standard annotations in WebViewer. Tailor your annotations to fit your needs and seamlessly view them across different platforms. The Apryse Web SDK streamlines s

Custom annotations are non-standard annotations typically defined by the user and beyond the PDF specification. As such, they are only fully supported in the viewer they are implemented in. WebViewer provides the ability to create custom annotations that can be tailored to your needs. The custom annotations can even be viewed in other viewers and loaded back into WebViewer as it's custom type.

## Creating the custom annotation class

Creating a class of a custom annotation is the most straightforward way to start creating a custom annotation. By extending from the [`CustomAnnotation`](https://sdk.apryse.com/api/web/Core.Annotations.CustomAnnotation.html) class, you automatically gain the benefits of preserving the annotation in other compliant viewers.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(
  // ...
).then(function(instance) {
  const { Annotations } = instance.Core;

  class TriangleAnnotation extends Annotations.CustomAnnotation {
    constructor() {
      super('triangle'); // provide the custom XFDF element name
      this.Subject = 'Triangle';
    }
  }

  // this is necessary to set the elementName before instantiation
  TriangleAnnotation.prototype.elementName = 'triangle';
});
```

{% endcode %}

[Annotations.CustomAnnotation](https://sdk.apryse.com/api/web/Core.Annotations.CustomAnnotation.html)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
WebViewer(
  // ...
).then(function(instance) {
  const { Annotations } = instance;

  class TriangleAnnotation extends Annotations.CustomAnnotation {
    constructor() {
      super('triangle'); // provide the custom XFDF element name
      this.Subject = 'Triangle';
    }
  }

  // this is necessary to set the elementName before instantiation
  TriangleAnnotation.prototype.elementName = 'triangle';
});
```

{% endcode %}

[Annotations.CustomAnnotation](https://sdk.apryse.com/api/web/Core.Annotations.CustomAnnotation.html)
{% endtab %}
{% endtabs %}

Just to emphasize, setting the `elementName` on the class prototype is ncessary for your custom annotation to be properly recognized.

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

```sh
TriangleAnnotation.prototype.elementName = 'triangle';
```

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

### Defining how it renders

Next, let's define the `draw` function on the class so that the annotation knows how to render itself. The `draw` function takes a canvas context and is called whenever the annotation should be drawn.

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

```js
class TriangleAnnotation extends Annotations.CustomAnnotation {
  // ...
  draw(ctx, pageMatrix) {
    // the setStyles function is a function on markup annotations that sets up
    // certain properties for us on the canvas for the annotation's stroke thickness.
    this.setStyles(ctx, pageMatrix);

    // first we need to translate to the annotation's x/y coordinates so that it's
    // drawn in the correct location
    ctx.translate(this.X, this.Y);
    ctx.beginPath();
    ctx.moveTo(this.Width / 2, 0);
    ctx.lineTo(this.Width, this.Height);
    ctx.lineTo(0, this.Height);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
  }
}
```

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

[Annotations.Annotation.draw](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#draw__anchor)

### Registering the custom annotation

Lastly, we want to register our annotation type so that the `AnnotationManager` recognizes our custom type when reading and outputting XFDF.

{% tabs %}
{% tab title="JavaScript (SDK v8.0+)" %}
{% code lineNumbers="true" %}

```js
const { annotationManager } = instance.Core;

// ...

// register the annotation type so that it can be saved to XFDF files
annotationManager.registerAnnotationType(TriangleAnnotation.prototype.elementName, TriangleAnnotation);
```

{% endcode %}

[AnnotationManager.registerAnnotationType](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#registerAnnotationType)
{% endtab %}

{% tab title="JavaScript (SDK v6.0+)" %}
{% code lineNumbers="true" %}

```js
const { annotManager } = instance;

// ...

// register the annotation type so that it can be saved to XFDF files
annotManager.registerAnnotationType(TriangleAnnotation.prototype.elementName, TriangleAnnotation);
```

{% endcode %}

[AnnotationManager.registerAnnotationType](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#registerAnnotationType)
{% endtab %}
{% endtabs %}

## Creating a custom annotation from an existing annotation class

Sometimes, you may want to create a custom annotation class or extend from an existing class on the fly. WebViewer provides the [`createFromClass`](https://sdk.apryse.com/api/web/Core.Annotations.CustomAnnotation.html#.createFromClass) API to generate custom annotation classes from an existing anntoation class. This differs from extending existing annotations since the output is a custom annotation class that receives the same benefits from extending `CustomAnnotation`.

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

```js
const MyRectangle = CustomAnnotation.createFromClass('myrect', Annotations.RectangleAnnotation);
```

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

You can then use our [`setCustomDrawHandler`](/web/annotation/customize/customize-rendering.md) or [`setCustomSerializeHandler`](/web/annotation/customize/customize-serialization.md) APIs to change how the annotation class behaves.

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

```js
// Change how the custom annotation renders
Annotations.setCustomDrawHandler(MyRectangle, function(ctx, pageMatrix, rotation, options) {
    options.originalDraw(ctx, pageMatrix, rotation);
    ctx.save();
    ctx.fillStyle = '#000000';
    ctx.fillText('Hello', this.X, this.Y + 12);
    ctx.restore();
});
```

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

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

This gets you completely different, custom annotation class that will behave similar to the original while being a custom annotation.

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

```js
const rect = new MyRectangle({
  X: 50,
  Y: 50,
  Width: 150,
  Height: 50,
});
```

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

{% hint style="info" %}
You will still need to register this annotation.
{% endhint %}

## Stamp image settings

You might have noticed if you open this custom annotation in another viewer, the triangle edges are cut off and it may look lower res. This is because the custom annotation is saved as a stamp annotation and the edges of the triangle are rendered past the bounds of the annotation.

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

There are two static properties you can tweak to adjust this: `OutputImagePadding` and `QualityScale`.

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

```js
TriangleAnnotation.OutputImagePadding = 25; // adds 25 pixels all around
TriangleAnnotation.QualityScale = 2; // doubles the resolution at the cost of memory
```

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

Please note that adding too much padding may scale down the perceived image. These options will not affect your WebViewer as the custom render logic is available there.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-9ab7bfc2c0876e812bd686414f12c3c2ed3e6c62%2F09cf2448fdbab0abccdd14e300a07ea75d1f3e53-408x481.png?alt=media)

## Using Serialized Data

Using the annotation's custom data is useful for storing custom data. With `CustomAnnotation`, there is a `SerializedData` property that will automatically save the data attached to it. It is better to use this for primitive values rather than for complex objects.

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

```js
class TriangleAnnotation extends Annotations.CustomAnnotation {
  // custom property
  get CustomID() {
    // attempt to get a customId value from the map
    return this.SerializedData.customId;
  }
  set CustomID(id) {
    // set a customId value from the map
    this.SerializedData.customId = id;
  }
}
```

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

## Saving custom XFDF

There may be some cases where you would prefer the XFDF to reflect the actual type of the custom annotation and not a stamp. For example, if you are only saving the XFDF of the annotations as opposed to the document. In this case, you can switch the static `SerializationType` property on the `CustomAnnotation` class from `STAMP` to `CUSTOM`. Please note that this will affect annotations of the same type and the custom XFDF will be discarded when merging with the document. If you are downloading the document, be sure to switch it back to stamp temporarily.

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

```js
TriangleAnnotation.SerializationType = Annotations.CustomAnnotation.SerializationTypes.CUSTOM; // use custom XFDF
```

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

Instead of a stamp in the XFDF:

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

```xml
<stamp page="0" rect="131.96,227.76999999999998,294.27,407.23" color="#000000" flags="print" name="bb8ac8fa-ff92-08ff-c2e5-90dbaeb9edde" title="Guest" subject="Triangle" date="D:20210319141059-07'00'" creationdate="D:20210319140524-07'00'">
    <trn-custom-data bytes="..."/>
    <imagedata>data:image/png;base64,...</imagedata>
</stamp>
```

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

Your output XFDF should then look like this:

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

```xml
<triangle page="0" rect="131.96,227.76999999999998,294.27,407.23" color="#000000" flags="print" name="bb8ac8fa-ff92-08ff-c2e5-90dbaeb9edde" title="Guest" subject="Triangle" date="D:20210319141059-07'00'" creationdate="D:20210319140524-07'00'">
    <trn-custom-data bytes="..."/>
</triangle>
```

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

## Next steps

Read the full tutorial on how to [create a custom triangle annotation](/web/annotation/customize/custom-annotations.md) or check out how to [alter annotation rendering](/web/annotation/customize/creating-custom-annotations.md).


---

# 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/annotation/customize/creating-custom-annotations.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.
