Custom PDF annotation appearances

WebViewer supports adding a PDF object or page as an appearance to any annotation type. This allows an annotation to be displayed in a custom way which overrides the default rendering based on the annotation properties. This custom appearance is also compatible with the PDF specification so after downloading the file the annotation will appear identically in other PDF viewers.

The addCustomAppearance function is defined on all annotations and expects a PDF Document object to be passed in along with an optional page number or PDF object number. The normal annotation appearance will then be overridden by the PDF document's content when the file is downloaded.

1WebViewer({
2 // options
3}, document.getElementById('viewer'))
4 .then((instance) => {
5 const { Annotations, documentViewer } = instance.Core;
6
7 documentViewer.addEventListener('documentLoaded', async () => {
8 const rectangle = new Annotations.RectangleAnnotation();
9 rectangle.PageNumber = 1;
10 rectangle.X = 10;
11 rectangle.Y = 150;
12 rectangle.Width = 235;
13 rectangle.Height = 200;
14 rectangle.FillColor = new Annotations.Color(0, 0, 0);
15
16 // note that if you are adding multiple appearances you should make sure they have unique file names
17 const doc = await instance.Core.createDocument('https://pdftron.s3.amazonaws.com/downloads/pl/tiger.pdf', {
18 useDownloader: false,
19 filename: 'tiger.pdf'
20 });
21 rectangle.addCustomAppearance(doc, { pageNumber: 1 });
22
23 documentViewer.getAnnotationManager().addAnnotation(rectangle);
24 documentViewer.getAnnotationManager().redrawAnnotation(rectangle);
25 });
26 });

Then you can see the rectangle has the appearance of the tiger PDF document.

Apryse Docs Image

Custom appearances with XFDF

If you download the PDF using getFileData then the annotation appearances will be saved with the PDF and visible in other PDF viewers.

However if you save your annotations separately from the PDF as XFDF then you'll need to use the annotManager.setCustomAppearanceHandler API so that the appearance can be reloaded into WebViewer when you import the XFDF. The XFDF only contains a reference to the appearance name and not the entire contents of the PDF file describing the appearance.

1WebViewer({
2 // options
3}, document.getElementById('viewer'))
4 .then((instance) => {
5 const { annotationManager } = instance.Core;
6
7 annotationManager.setCustomAppearanceHandler(async (filename) => {
8 // filename is the name of the appearance
9
10 // this is assuming that you have saved the file referenced by the appearances somewhere on your server with the same filename
11 return instance.Core.createDocument(`https://pdftron.s3.amazonaws.com/downloads/pl/${filename}`, { useDownloader: false });
12 });
13
14 // later call annotationManager.importAnnotations or annotationManager.importAnnotCommand
15 });

Vector appearances

It is possible to create vector quality custom annotations using Apryse's CanvasToPDF library.

To do this, install the @pdftron/canvas-to-pdf npm package and import the canvasToPDF function. This function accepts a draw handler containing canvas drawing commands and outputs a blob representing a PDF with vector graphics. Convert this blob into a PDF Document object then pass it as a parameter to addCustomAppearance to create a vector appearance.

JavaScript (v8.0+)

1WebViewer({
2 // options
3}, document.getElementById('viewer')).then((instance) => {
4 const { Annotations, annotationManager, documentViewer } = instance.Core;
5
6 const annotWidth = 600;
7 const annotHeight = 600;
8
9 documentViewer.addEventListener('documentLoaded', async () => {
10 const rectangleAnnot = new Annotations.RectangleAnnotation({
11 PageNumber: 1,
12 // values are in page coordinates with (0, 0) in the top left
13 X: 0,
14 Y: 0,
15 Width: annotWidth,
16 Height: annotHeight,
17 Author: annotationManager.getCurrentUser(),
18 });
19
20 const draw = (ctx) => {
21 for (let i = 0; i < 15; i++) {
22 for (let j = 0; j < 15; j++) {
23 ctx.strokeStyle = `rgb( 0, ${Math.floor(255 - 42.5 * i)}, ${Math.floor(255 - 42.5 * j)})`;
24 ctx.beginPath();
25 ctx.arc(25 + j * 40, 25 + i * 40, 15, 0, Math.PI * 2, true);
26 ctx.stroke();
27 }
28 }
29 };
30
31 const blob = await canvasToPDF(draw, {
32 width: rectangleAnnot.Width,
33 height: rectangleAnnot.Height,
34 });
35 const doc = await instance.Core.createDocument(blob, {
36 extension: 'pdf',
37 });
38
39 rectangleAnnot.addCustomAppearance(doc, { pageNumber: 1 });
40
41 annotationManager.addAnnotation(rectangleAnnot);
42 annotationManager.redrawAnnotation(rectangleAnnot);
43 });
44});

Then you can verify that the appearance is vector quality.

Apryse Docs Image

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales