Custom Annotations in WebViewer

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 class, you automatically gain the benefits of preserving the annotation in other compliant viewers.

1WebViewer(
2 // ...
3).then(function(instance) {
4 const { Annotations } = instance.Core;
5
6 class TriangleAnnotation extends Annotations.CustomAnnotation {
7 constructor() {
8 super('triangle'); // provide the custom XFDF element name
9 this.Subject = 'Triangle';
10 }
11 }
12
13 // this is necessary to set the elementName before instantiation
14 TriangleAnnotation.prototype.elementName = 'triangle';
15});

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

sh

1TriangleAnnotation.prototype.elementName = 'triangle';

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.

JavaScript

1class TriangleAnnotation extends Annotations.CustomAnnotation {
2 // ...
3 draw(ctx, pageMatrix) {
4 // the setStyles function is a function on markup annotations that sets up
5 // certain properties for us on the canvas for the annotation's stroke thickness.
6 this.setStyles(ctx, pageMatrix);
7
8 // first we need to translate to the annotation's x/y coordinates so that it's
9 // drawn in the correct location
10 ctx.translate(this.X, this.Y);
11 ctx.beginPath();
12 ctx.moveTo(this.Width / 2, 0);
13 ctx.lineTo(this.Width, this.Height);
14 ctx.lineTo(0, this.Height);
15 ctx.closePath();
16 ctx.fill();
17 ctx.stroke();
18 }
19}

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.

1const { annotationManager } = instance.Core;
2
3// ...
4
5// register the annotation type so that it can be saved to XFDF files
6annotationManager.registerAnnotationType(TriangleAnnotation.prototype.elementName, TriangleAnnotation);

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

JavaScript (v8.0+)

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

You can then use our setCustomDrawHandler or setCustomSerializeHandler APIs to change how the annotation class behaves.

JavaScript (v8.0+)

1// Change how the custom annotation renders
2Annotations.setCustomDrawHandler(MyRectangle, function(ctx, pageMatrix, rotation, options) {
3 options.originalDraw(ctx, pageMatrix, rotation);
4 ctx.save();
5 ctx.fillStyle = '#000000';
6 ctx.fillText('Hello', this.X, this.Y + 12);
7 ctx.restore();
8});

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

JavaScript (v8.0+)

1const rect = new MyRectangle({
2 X: 50,
3 Y: 50,
4 Width: 150,
5 Height: 50,
6});

You will still need to register this annotation.

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.

Apryse Docs Image

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

JavaScript

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

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.

Apryse Docs Image

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.

JavaScript

1class TriangleAnnotation extends Annotations.CustomAnnotation {
2 // custom property
3 get CustomID() {
4 // attempt to get a customId value from the map
5 return this.SerializedData.customId;
6 }
7 set CustomID(id) {
8 // set a customId value from the map
9 this.SerializedData.customId = id;
10 }
11}

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.

JavaScript

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

Instead of a stamp in the XFDF:

XML

1<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'">
2 <trn-custom-data bytes="..."/>
3 <imagedata>data:image/png;base64,...</imagedata>
4</stamp>

Your output XFDF should then look like this:

XML

1<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'">
2 <trn-custom-data bytes="..."/>
3</triangle>

Next steps

Read the full tutorial on how to create a custom triangle annotation or check out how to alter annotation rendering.

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales