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

# Creating custom annotations for your viewer

Create unique annotations for your viewer with WebViewer. Customize appearance and behavior of annotations, selection box, and control handles. Learn to create a custom triangle annotation effortlessl

WebViewer allows you to create your own annotations that can be customized in several different ways. You can change the appearance and behaviors of the annotation, selection box, and control handles. As an example of this, we're going to walk through the steps to create a custom triangle annotation.

To save annotations in WebViewer, they must output XFDF that meets the [XFDF specification](https://www.xodo.com/view/#/203b4538-396c-4a36-9ce3-968e3c3623f6) in order to function properly in compliant viewers. Since this is a custom annotation, it would not render and behave the same in another viewer as it will not conform to the XFDF specfication. Nor will the viewer have the custom logic to handle it. However, WebViewer can automatically handle converting the custom annotation to a stamp annotation (and vice-versa) to preserve the appearance of the page as much as possible. All custom rendering and behavior will only work in WebViewer, where you have custom logic to handle it.

## Creating the custom annotation class

First let's create a basic triangle annotation class.

{% 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 %}

We'll have it inherit from `Annotations.CustomAnnotation` and set the XFDF element name to `triangle`. The element name is what's used for the annotation's XML element in the [XFDF](/web/annotation/xfdf.md). Notice that `triangle` is not in the XFDF specification so this normally would not work. By inheriting from the `CustomAnnotation` class, the annotation will be able to automatically take advantage of saving as a stamp when downloading the document. This will allow the custom annotation to appear similar to how it appears in WebViewer in another viewer.

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)

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 %}

## Adding the annotation to a document

Although we can programmatically create and add this annotation, it would not be intuitive for regular users. To allow a user to actually add the annotation to a document, we'll need to create a [tool](/web/annotation/annotations-and-tools.md#what-are-tools) so that the user can use to create our annotation through the UI. Our triangle just depends on two mouse points so we can inherit from the `GenericAnnotationCreateTool` which handles that for us.

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

```js
// we also need to access the Tools namespace from the instance
const { Annotations, Tools } = instance.Core;

// ...

class TriangleCreateTool extends Tools.GenericAnnotationCreateTool {
  constructor(documentViewer) {
    // TriangleAnnotation is the class (function) for our annotation we defined previously
    super(documentViewer, TriangleAnnotation);
  }
};
```

{% endcode %}

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

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

```js
// we also need to access the Tools namespace from the instance
const { Annotations, Tools } = instance;

// ...

class TriangleCreateTool extends Tools.GenericAnnotationCreateTool {
  constructor(docViewer) {
    // TriangleAnnotation is the class (function) for our annotation we defined previously
    super(docViewer, TriangleAnnotation);
  }
};
```

{% endcode %}

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

With our tool created we can add a button to the UI so that it can be switched to.

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

```js
// access annotationManager and documentViewer objects from the instance
const { Annotations, Tools, annotationManager, documentViewer } = instance.Core;

// ...

const triangleToolName = 'AnnotationCreateTriangle';

const triangleTool = new TriangleCreateTool(documentViewer);
instance.UI.registerTool({
  toolName: triangleToolName,
  toolObject: triangleTool,
  buttonImage: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' +
    '<path d="M12 7.77L18.39 18H5.61L12 7.77M12 4L2 20h20L12 4z"/>' +
    '<path fill="none" d="M0 0h24v24H0V0z"/>' +
  '</svg>',
  buttonName: 'triangleToolButton',
  tooltip: 'Triangle'
}, TriangleAnnotation);

instance.UI.setHeaderItems((header) => {
  header.getHeader('toolbarGroup-Shapes').get('freeHandToolGroupButton').insertBefore({
    type: 'toolButton',
    toolName: triangleToolName
  });
});

documentViewer.addEventListener('documentLoaded', () => {
  // set the tool mode to our tool so that we can start using it right away
  instance.UI.setToolMode(triangleToolName);
});
```

{% endcode %}

[WebViewerInstance.UI.registerTool](https://sdk.apryse.com/api/web/UI.html#registerTool__anchor) [WebViewerInstance.UI.setHeaderItems](https://sdk.apryse.com/api/web/UI.html#setHeaderItems__anchor) [WebViewerInstance.UI.setToolMode](https://sdk.apryse.com/api/web/UI.html#setToolMode__anchor)
{% endtab %}

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

```js
// access annotManager and docViewer objects from the instance
const { Annotations, Tools, annotManager, docViewer } = instance;

// ...

const triangleToolName = 'AnnotationCreateTriangle';

const triangleTool = new TriangleCreateTool(docViewer);
instance.registerTool({
  toolName: triangleToolName,
  toolObject: triangleTool,
  buttonImage: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">' +
    '<path d="M12 7.77L18.39 18H5.61L12 7.77M12 4L2 20h20L12 4z"/>' +
    '<path fill="none" d="M0 0h24v24H0V0z"/>' +
  '</svg>',
  buttonName: 'triangleToolButton',
  tooltip: 'Triangle'
}, TriangleAnnotation);

instance.setHeaderItems((header) => {
  header.getHeader('toolbarGroup-Shapes').get('freeHandToolGroupButton').insertBefore({
    type: 'toolButton',
    toolName: triangleToolName
  });
});

docViewer.on('documentLoaded', () => {
  // set the tool mode to our tool so that we can start using it right away
  instance.setToolMode(triangleToolName);
});
```

{% endcode %}

[WebViewerInstance.registerTool](https://sdk.apryse.com/api/web/UI.html#registerTool__anchor) [WebViewerInstance.setHeaderItems](https://sdk.apryse.com/api/web/UI.html#setHeaderItems__anchor) [WebViewerInstance.setToolMode](https://sdk.apryse.com/api/web/UI.html#setToolMode__anchor)
{% endtab %}
{% endtabs %}

At this point you should see a new button in the toolbar with a triangle icon, and the new triangle tool should be automatically selected. Clicking and dragging on the document should create a triangle annotation.

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-22b88f5e207aa99f6f8d3c09492aee4930d3c9d5%2F46906c9af16fce60bc3b3d7c8f48155c32a6dcd6-433x441.png?alt=media)

After creating some triangles you might notice that the selection box is a rectangle and has eight control handles. This isn't terrible but we could probably make it better by having a control handle for each corner and drawing the selection box around the edges of the annotation.

## Making customizable vertices

First, we will add a property on the annotation that takes an array of vertices which can be adjusted individually by a user moving the control points. Then define a new selection model and control handles to resize the annotation. A [SelectionModel](https://sdk.apryse.com/api/web/Core.Annotations.SelectionModel.html) defines the selection behavior of the annotation.

We'll add the array to the annotation constructor:

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

```js
// you can also get Core from the instance
const { Core } = instance;

class TriangleAnnotation extends Annotations.CustomAnnotation {
  constructor() {
    super('triangle'); // Provide the custom XFDF element name
    this.Subject = 'Triangle';
    // create simple property
    this.vertices = [];
    const numVertices = 3;
    // initialize points
    for (let i = 0; i < numVertices; ++i) {
      this.vertices.push(new Core.Math.Point());
    }
  }
}
```

{% endcode %}

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

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

```js
// you can also get CoreControls from the instance
const { CoreControls } = instance;

class TriangleAnnotation extends Annotations.CustomAnnotation {
  constructor() {
    super('triangle'); // Provide the custom XFDF element name
    this.Subject = 'Triangle';
    // create simple property
    this.vertices = [];
    const numVertices = 3;
    // initialize points
    for (let i = 0; i < numVertices; ++i) {
      this.vertices.push(new CoreControls.Math.Point());
    }
  }
}
```

{% endcode %}

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

Then we'll update the draw function on the annotation to use the `vertices`:

{% 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);

    // draw the triangle lines using vertices from our list
    ctx.beginPath();
    ctx.moveTo(this.vertices[0].x, this.vertices[0].y);
    ctx.lineTo(this.vertices[1].x, this.vertices[1].y);
    ctx.lineTo(this.vertices[2].x, this.vertices[2].y);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();
  }
}
```

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

Then for the tool we'll override the mouseMove function to set the `vertices` on creation:

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

```js
class TriangleCreateTool extends Tools.GenericAnnotationCreateTool {
  // ...
  mouseMove(e) {
    // call the parent mouseMove first
    super.mouseMove(e);
    if (this.annotation) {
      // set the vertices relative to the annotation width and height
      this.annotation.vertices[0].x = this.annotation.X + this.annotation.Width / 2;
      this.annotation.vertices[0].y = this.annotation.Y;
      this.annotation.vertices[1].x = this.annotation.X + this.annotation.Width;
      this.annotation.vertices[1].y = this.annotation.Y + this.annotation.Height;
      this.annotation.vertices[2].x = this.annotation.X;
      this.annotation.vertices[2].y = this.annotation.Y + this.annotation.Height;

      // update the annotation appearance
      annotationManager.redrawAnnotation(this.annotation);
    }
  }
}
```

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

[Tools.Tool.mouseMove](https://sdk.apryse.com/api/web/Core.Tools.Tool.html#mouseMove) [AnnotationManager.redrawAnnotation](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#redrawAnnotation__anchor)

## Creating a custom selection model

At this point the drawing of the annotation should look the same as before, however you won't be able to move the annotation. To fix this, let us create the custom selection model and control handles. Since the selection model needs us to define which type of control handles are used, we will start by defining the custom control handles.

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

```js
class TriangleControlHandle extends Annotations.ControlHandle {
  constructor(annotation, index) {
    super();
    this.annotation = annotation;
    // set the index of this control handle so that we know which vertex it corresponds to
    this.index = index;
  }
  // returns a rect that should represent the control handle's position and size
  getDimensions(annotation, selectionBox, zoom) {
    let x = annotation.vertices[this.index].x;
    let y = annotation.vertices[this.index].y;
    // account for zoom level
    const width = Annotations.ControlHandle.handleWidth / zoom;
    const height = Annotations.ControlHandle.handleHeight / zoom;

    // adjust for the control handle's own width and height
    x -= width * 0.5;
    y -= height * 0.5;
    return new Core.Math.Rect(x, y, x + width, y + height);
  }
  // this function is called when the control handle is dragged
  move(annotation, deltaX, deltaY, fromPoint, toPoint) {
    annotation.vertices[this.index].x += deltaX;
    annotation.vertices[this.index].y += deltaY;

    // recalculate the X, Y, width and height of the annotation
    let minX = Number.MAX_VALUE;
    let maxX = -Number.MAX_VALUE;
    let minY = Number.MAX_VALUE;
    let maxY = -Number.MAX_VALUE;
    for (let i = 0; i < annotation.vertices.length; ++i) {
      const vertex = annotation.vertices[i];
      minX = Math.min(minX, vertex.x);
      maxX = Math.max(maxX, vertex.x);
      minY = Math.min(minY, vertex.y);
      maxY = Math.max(maxY, vertex.y);
    }

    const rect = new Annotations.Rect(minX, minY, maxX, maxY);
    annotation.setRect(rect);
    // return true if redraw is needed
    return true;
  }
}
```

{% endcode %}

[Annotations.SelectionModel.getDimensions](https://sdk.apryse.com/api/web/Core.Annotations.SelectionModel.html#getDimensions) [Core.Math.Rect](https://sdk.apryse.com/api/web/Core.Math.Rect.html) [Annotations.Annotation.setRect](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#setRect)
{% endtab %}

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

```js
class TriangleControlHandle extends Annotations.ControlHandle {
  constructor(annotation, index) {
    super();
    this.annotation = annotation;
    // set the index of this control handle so that we know which vertex it corresponds to
    this.index = index;
  }
  // returns a rect that should represent the control handle's position and size
  getDimensions(annotation, selectionBox, zoom) {
    let x = annotation.vertices[this.index].x;
    let y = annotation.vertices[this.index].y;
    // account for zoom level
    const width = Annotations.ControlHandle.handleWidth / zoom;
    const height = Annotations.ControlHandle.handleHeight / zoom;

    // adjust for the control handle's own width and height
    x -= width * 0.5;
    y -= height * 0.5;
    return new CoreControls.Math.Rect(x, y, x + width, y + height);
  }
  // this function is called when the control handle is dragged
  move(annotation, deltaX, deltaY, fromPoint, toPoint) {
    annotation.vertices[this.index].x += deltaX;
    annotation.vertices[this.index].y += deltaY;

    // recalculate the X, Y, width and height of the annotation
    let minX = Number.MAX_VALUE;
    let maxX = -Number.MAX_VALUE;
    let minY = Number.MAX_VALUE;
    let maxY = -Number.MAX_VALUE;
    for (let i = 0; i < annotation.vertices.length; ++i) {
      const vertex = annotation.vertices[i];
      minX = Math.min(minX, vertex.x);
      maxX = Math.max(maxX, vertex.x);
      minY = Math.min(minY, vertex.y);
      maxY = Math.max(maxY, vertex.y);
    }

    const rect = new Annotations.Rect(minX, minY, maxX, maxY);
    annotation.setRect(rect);
    // return true if redraw is needed
    return true;
  }
}
```

{% endcode %}

[Annotations.SelectionModel.getDimensions](https://sdk.apryse.com/api/web/Core.Annotations.SelectionModel.html#getDimensions) [CoreControls.Math.Rect](https://sdk.apryse.com/api/web/Core.Math.Rect.html) [Annotations.Annotation.setRect](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#setRect)
{% endtab %}
{% endtabs %}

Then we can define our selection model that use our custom control point.

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

```js
// selection model creates the necessary control handles
class TriangleSelectionModel extends Annotations.SelectionModel {
  constructor(annotation, canModify) {
    super(annotation, canModify);
    if (canModify) {
      const controlHandles = this.getControlHandles();
      // pass the vertex index to each control handle
      controlHandles.push(new TriangleControlHandle(annotation, 0));
      controlHandles.push(new TriangleControlHandle(annotation, 1));
      controlHandles.push(new TriangleControlHandle(annotation, 2));
    }
  }
}
```

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

Finally, we can assign this new selection model as the selection model for our triangle. Notice we assign the class instead of an instance since this the selection model is dynamically created.

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

```js
class TriangleAnnotation extends Annotations.CustomAnnotation {
  constructor() {
    // ...
    this.selectionModel = TriangleSelectionModel;
  }
}
```

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

Now there should be a control handle for each point of the triangle and if you drag them around you'll move that vertex of the triangle! However you may notice that if you try to drag and move the annotation it won't work. To fix this let's override the `resize` function on the annotation.

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

```js
class TriangleAnnotation extends Annotations.CustomAnnotation {
  // ...
  resize(rect) {
    // this function is only called when the annotation is dragged
    // since we handle the case where the control handles move
    const annotRect = this.getRect();
    // determine how much change in each dimension
    const deltaX = rect.x1 - annotRect.x1;
    const deltaY = rect.y1 - annotRect.y1;

    // shift the vertices by the amount the rect has shifted
    this.vertices = this.vertices.map((vertex) => {
      vertex.translate(deltaX, deltaY);
      return vertex;
    });
    this.setRect(rect);
  }
}
```

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

[Annotations.Annotation.resize](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#resize) [Core.Math.Point](https://sdk.apryse.com/api/web/Core.Math.Rect.html#translate__anchor)

Next, let's change the selection box so that it's displayed around the sides of the triangle. We'll do this by overriding the `drawSelectionOutline` function on the selection model.

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

```js
class TriangleSelectionModel extends Annotations.SelectionModel {
  // ...
  // changes how we draw the selection outline
  drawSelectionOutline(ctx, annotation, zoom) {
    // adjust for zoom
    if (typeof zoom !== 'undefined') {
      ctx.lineWidth = Annotations.SelectionModel.selectionOutlineThickness / zoom;
    } else {
      ctx.lineWidth = Annotations.SelectionModel.selectionOutlineThickness;
    }

    // changes the selection outline color if the user doesn't have permission to modify this annotation
    if (this.canModify()) {
      ctx.strokeStyle = Annotations.SelectionModel.defaultSelectionOutlineColor.toString();
    } else {
      ctx.strokeStyle = Annotations.SelectionModel.defaultNoPermissionSelectionOutlineColor.toString();
    }

    ctx.beginPath();
    ctx.moveTo(annotation.vertices[0].x, annotation.vertices[0].y);
    ctx.lineTo(annotation.vertices[1].x, annotation.vertices[1].y);
    ctx.lineTo(annotation.vertices[2].x, annotation.vertices[2].y);
    ctx.closePath();
    ctx.stroke();

    // draw a dashed line around the triangle
    const dashUnit = Annotations.SelectionModel.selectionOutlineDashSize / zoom;
    const sequence = [dashUnit, dashUnit];
    ctx.setLineDash(sequence);
    ctx.strokeStyle = 'rgb(255, 255, 255)';
    ctx.stroke();
  }
  // change the selection testing to match the shape of the triangle
  testSelection(annotation, x, y, pageMatrix) {
    // the canvas visibility test will only select the annotation
    // if a user clicks exactly on it as opposed to the rectangular bounding box
    return Annotations.SelectionAlgorithm.canvasVisibilityTest(annotation, x, y, pageMatrix);
  }
}
```

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

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

For fun, let's also override the control handle's draw function to make them look like triangles as well.

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

```js
class TriangleControlHandle extends Annotations.ControlHandle {
  // ...
  draw(ctx, annotation, selectionBox, zoom) {
    const dim = this.getDimensions(annotation, selectionBox, zoom);
    ctx.fillStyle = '#FFFFFF';
    ctx.beginPath();
    ctx.moveTo(dim.x1 + (dim.getWidth() / 2), dim.y1);
    ctx.lineTo(dim.x1 + dim.getWidth(), dim.y1 + dim.getHeight());
    ctx.lineTo(dim.x1, dim.y1 + dim.getHeight());
    ctx.closePath();
    ctx.stroke();
    ctx.fill();
  }
}
```

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

If everything went well you should have triangle annotations that look something like this:

![](https://3532544125-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FX9YnTSKIHvV7m0A36LbO%2Fuploads%2Fgit-blob-346ebc4c2c4af4f4ad730eb27be4d552155a8f7d%2Fbcca7e8e96ede3f9bff562f834aade4ffd829abb-363x200.png?alt=media)

## Saving the custom annotation

As mentioned early on, the `CustomAnnotation` class does handle saving our custom type as a stamp and automatically reloads the stamp as the custom type if it is registered. However, it will only preserve our type and our custom `vertices` property needs to be persisted as well.

If you download the document now and open it in another viewer, you will see the stamp of your custom annotation. If you tried to load this document or import the annotation through XFDF, you would notice that it isn't able to be reloaded. This is because WebViewer doesn't know that it needs to save the `vertices` array. We also need to save the `vertices` into the XFDF but we also run into another issue: `vertices` is not in the specification or part of the stamp.

Thus, we will need to save this into the annotation's custom data. To do this we can override the serialize and deserialize functions which are called when the annotation should be saved or loaded respectively.

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

```js
class TriangleAnnotation extends Annotations.CustomAnnotation {
  // ...
  serialize(element, pageMatrix) {
    // save our custom property into the custom data
    this.setCustomData('vertices', this.vertices);
    // perform regular serialization on other properties
    const el = super.serialize(element, pageMatrix);
    return el;
  }
  deserialize(element, pageMatrix) {
    // perform regular deserialization for other properties
    super.deserialize(element, pageMatrix);
    // read our custom property out from custom data
    const storedVertices = this.getCustomData('vertices');
    // set the property after initializing the data as points
    this.vertices = storedVertices.map(v => new Core.Math.Point(v.x, v.y));
  }
}
```

{% endcode %}

[Annotations.Annotation.serialize](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#serialize__anchor) [Annotations.Annotation.deserialize](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#deserialize__anchor) [Annotations.Annotation.setCustomData](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#setCustomData__anchor) [Annotations.Annotation.getCustomData](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#getCustomData__anchor)
{% endtab %}

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

```js
class TriangleAnnotation extends Annotations.CustomAnnotation {
  // ...
  serialize(element, pageMatrix) {
    // save our custom property into the custom data
    this.setCustomData('vertices', this.vertices);
    // perform regular serialization on other properties
    const el = super.serialize(element, pageMatrix);
    return el;
  }
  deserialize(element, pageMatrix) {
    // perform regular deserialization for other properties
    super.deserialize(element, pageMatrix);
    // read our custom property out from custom data
    const storedVertices = this.getCustomData('vertices');
    // set the property after initializing the data as points
    this.vertices = storedVertices.map(v => new CoreControls.Math.Point(v.x, v.y));
  }
}
```

{% endcode %}

[Annotations.Annotation.serialize](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#serialize__anchor) [Annotations.Annotation.deserialize](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#deserialize__anchor) [Annotations.Annotation.setCustomData](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#setCustomData__anchor) [Annotations.Annotation.getCustomData](https://sdk.apryse.com/api/web/Core.Annotations.Annotation.html#getCustomData__anchor)
{% endtab %}
{% endtabs %}

After making this change you should be able to [export XFDF](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#exportAnnotations__anchor) and [import](https://sdk.apryse.com/api/web/Core.AnnotationManager.html#importAnnotations__anchor) the string back. You should also be able to download the document and reload it with your exact annotation still there. Viewing this annotation in another viewer will show the annotation as a stamp. Changes to the stamp will like not affect your custom annotation after loading it back in WebViewer

## Stamp image settings (optional)

Now that you can save and load your custom annotation, 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 edges of the triangle are rendered past the bounds of the annotation and the image has been rasterized.

![](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 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.

For example, it would be better to store the number of vertices on this rather than the `vertices` since the vertices need to be transformed back into `Point`. It is still not impossible but carries some limitations.

{% 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 (optional)

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 %}

If you feel you want to add the custom properties to the XFDF (instead of custom data), feel free to include the following in your `serialize` and `deserialize` functions:

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

```js
class TriangleAnnotation extends Annotations.CustomAnnotation {
  serialize(element, pageMatrix) {
    const el = super.serialize(element, pageMatrix);
    // create an attribute to save the vertices list
    el.setAttribute('vertices', Annotations.XfdfUtils.serializePointArray(this.vertices, pageMatrix));
    return el;
  }
  deserialize(element, pageMatrix) {
    super.deserialize(element, pageMatrix);
    // read it back as points from the attribute
    this.vertices = Annotations.XfdfUtils.deserializePointArray(element.getAttribute('vertices'), pageMatrix);
  }
}
```

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


---

# 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/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.
