Integrate WebViewer in React to Create Vector-Quality Custom Annotations

Sample that demonstrates how to create vector-quality custom annotations leveraging Apryse's @pdftron/canvas-to-pdf library using React.

Just follow these steps:

  1. Define a custom annotation.
  2. Define a draw handler containing canvas drawing commands.
  3. Call canvasToPDF function that will accept the draw handler. This will output a blob representing a PDF with vector graphics.
  4. Convert the output blob into a PDF Document object.
  5. Pass the pdf document object to the custom annotation using addCustomAppearance function to create a vector appearance.

WebViewer provides a slick out-of-the-box responsive UI that enables you to view, annotate and manipulate PDFs and other document types inside any web project.

Click the button below to view the full project in GitHub.

1import { useRef, useEffect } from 'react';
2import WebViewer from '@pdftron/webviewer';
3import './App.css';
4import canvasToPDF from '@pdftron/canvas-to-pdf';
5import { drawTiger } from './drawTiger';
6
7const App = () => {
8 const viewer = useRef(null);
9
10 useEffect(() => {
11 WebViewer(
12 {
13 path: "/webviewer/lib",
14 initialDoc: "/files/PDFTron_about.pdf",
15 },
16 viewer.current
17 ).then(async (instance) => {
18 const { documentViewer, annotationManager, Annotations } = instance.Core;
19
20 const annotWidth = 600;
21 const annotHeight = 600;
22
23 documentViewer.addEventListener("documentLoaded", async () => {
24 const rectangleAnnot = new Annotations.RectangleAnnotation({
25 PageNumber: 1,
26 // values are in page coordinates with (0, 0) in the top left
27 X: 0,
28 Y: 0,
29 Width: annotWidth,
30 Height: annotHeight,
31 Author: annotationManager.getCurrentUser(),
32 });
33
34 const drawRect = (ctx) => {
35 const lineWidth = 20;
36 ctx.fillStyle = "red";
37 ctx.lineWidth = lineWidth;
38 ctx.strokeStyle = "black";
39 ctx.rect(
40 lineWidth / 2,
41 lineWidth / 2,
42 annotWidth - lineWidth,
43 annotHeight - lineWidth
44 );
45 ctx.fill();
46 ctx.stroke();
47 };
48
49 const drawGradientCircles = (ctx) => {
50 for (let i = 0; i < 15; i++) {
51 for (let j = 0; j < 15; j++) {
52 ctx.strokeStyle = `rgb(
53 0,
54 ${Math.floor(255 - 42.5 * i)},
55 ${Math.floor(255 - 42.5 * j)})`;
56 ctx.beginPath();
57 ctx.arc(25 + j * 40, 25 + i * 40, 15, 0, Math.PI * 2, true);
58 ctx.stroke();
59 }
60 }
61 };
62
63 const drawHatch = (ctx) => {
64 let X = 0;
65 let Y = 0;
66
67 ctx.save();
68
69 ctx.beginPath();
70 ctx.arc(
71 annotWidth * 0.5,
72 annotHeight * 0.5,
73 Math.max(annotHeight * 0.5, 0),
74 0,
75 Math.PI * 2,
76 false
77 );
78 ctx.closePath();
79 ctx.restore();
80 ctx.clip();
81
82 ctx.stroke();
83
84 const hatchSize = 10;
85 const hatchLineWidth = 1;
86 ctx.lineWidth = hatchLineWidth;
87
88 // horizontal lines
89 for (let i = Y; i < Y + annotHeight; i += hatchSize) {
90 ctx.beginPath();
91 ctx.moveTo(X, i);
92 ctx.lineTo(X + annotWidth, i);
93 ctx.stroke();
94 }
95
96 for (let i = X; i < X + annotWidth; i += hatchSize) {
97 ctx.beginPath();
98 ctx.moveTo(i, Y);
99 ctx.lineTo(i, Y + annotHeight);
100 ctx.stroke();
101 }
102 };
103
104 function rnd(min, max) {
105 return Math.floor(Math.random() * (max - min + 1) + min);
106 }
107
108 const drawTriangles = (ctx) => {
109 const canvasWidth = annotWidth;
110 const canvasHeight = annotHeight;
111 var heightScale = 0.866;
112
113 ctx.fillStyle = "rgb(0,0,0)";
114 ctx.fillRect(0, 0, annotWidth, annotHeight);
115 ctx.lineWidth = 1;
116
117 var hueStart = rnd(0, 360);
118 var triSide = 40;
119 var halfSide = triSide / 2;
120 var rowHeight = Math.floor(triSide * heightScale);
121 var columns = Math.ceil(canvasWidth / triSide) + 1;
122 var rows = Math.ceil(canvasHeight / rowHeight);
123
124 var col, row;
125 for (row = 0; row < rows; row++) {
126 var hue = hueStart + row * 3;
127
128 for (col = 0; col < columns; col++) {
129 var x = col * triSide;
130 var y = row * rowHeight;
131 var clr;
132
133 if (row % 2 !== 0) {
134 x -= halfSide;
135 }
136
137 // upward pointing triangle
138 clr = "hsl(" + hue + ", 50%, " + rnd(0, 60) + "%)";
139 ctx.fillStyle = clr;
140 ctx.strokeStyle = clr;
141 ctx.beginPath();
142 ctx.moveTo(x, y);
143 ctx.lineTo(x + halfSide, y + rowHeight);
144 ctx.lineTo(x - halfSide, y + rowHeight);
145 ctx.closePath();
146 ctx.fill();
147 ctx.stroke(); // needed to fill antialiased gaps on edges
148
149 // downward pointing triangle
150 clr = "hsl(" + hue + ", 50%, " + rnd(0, 60) + "%)";
151 ctx.fillStyle = clr;
152 ctx.strokeStyle = clr;
153 ctx.beginPath();
154 ctx.moveTo(x, y);
155 ctx.lineTo(x + triSide, y);
156 ctx.lineTo(x + halfSide, y + rowHeight);
157 ctx.closePath();
158 ctx.fill();
159 ctx.stroke();
160 }
161 }
162 };
163
164 const blob = await canvasToPDF(drawGradientCircles, {
165 width: rectangleAnnot.Width,
166 height: rectangleAnnot.Height,
167 });
168 const doc = await instance.Core.createDocument(blob, {
169 extension: "pdf",
170 });
171
172 rectangleAnnot.addCustomAppearance(doc, { pageNumber: 1 });
173
174 annotationManager.addAnnotation(rectangleAnnot);
175 // need to draw the annotation otherwise it won't show up until the page is refreshed
176 annotationManager.redrawAnnotation(rectangleAnnot);
177 });
178 });
179 }, []);
180
181 return (
182 <div className="App">
183 <div className="header">React sample</div>
184 <div className="webviewer" ref={viewer}></div>
185 </div>
186 );
187};
188
189export default App;
190

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales