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 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.
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
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. 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.
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 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.
1// we also need to access the Tools namespace from the instance
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.
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 defines the selection behavior of the annotation.
We'll add the array to the annotation constructor:
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.
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.
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.
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.
If everything went well you should have triangle annotations that look something like this:
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.
After making this change you should be able to export XFDF and import 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.
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 logic is available there.
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.
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
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: