Blog Post

How to Add Annotations to PDF Using Vue.js

Illustration: How to Add Annotations to PDF Using Vue.js

In this tutorial, you’ll explore how to leverage the pdf-lib library in combination with Vue.js to seamlessly add annotations to PDF documents.

In the first part of this tutorial, you’ll explore how to integrate the pdf-lib library with Vue.js to add annotations to existing PDF documents. In the second part of the tutorial, you’ll take your PDF annotation capabilities to the next level with PSPDFKit. PSPDFKit is a comprehensive PDF SDK that offers advanced tools for rendering, annotating, and manipulating PDFs with exceptional performance.

What Are PDF Annotations?

PDF annotations are interactive elements added to a PDF document without modifying its content. They include text, graphics, highlights, and more, facilitating document review, collaboration, and interactive features like forms.

With Vue.js and other frontend technologies, annotations can now be done directly within web applications, ensuring user privacy, as all processing occurs client-side. This approach offers a seamless and secure annotation experience, ideal for situations where data privacy is crucial.

To learn more about annotation types, check out our PDF annotations post.

Understanding pdf-lib

pdf-lib is a powerful JavaScript library that provides functionality for creating, modifying, and extracting data from PDF documents. It’s built on top of modern web technologies and offers a straightforward API to interact with PDFs programmatically. pdf-lib is versatile and well-suited for frontend projects, making it an excellent choice for adding annotations to PDFs within Vue.js applications.

Prerequisites

Before starting, ensure you have Node.js and npm (Node Package Manager) installed on your system. Also, it’s important to have a basic understanding of Vue.js and JavaScript.

Step 1 — Project Setup

  1. First, make sure you have Vue CLI installed globally. If not, you can install it using the following command:

npm install -g @vue/cli
  1. Create a Vue project using the Vue CLI by running the following command:

vue create pdf-modifier-app

This will ask some configuration questions.

  1. Select Default (Vue 3) ([Vue 3] babel, eslint) from the list, and change the directory to pdf-modifier-app:

cd pdf-modifier-app
  1. Navigate to the project directory and create a new file called PdfModifier.vue. This will be a Vue.js component, and it’ll be located in the src/components folder. The component will contain the template, script, and style sections.

Step 2 — Installing pdf-lib and axios

To use the pdf-lib library and fetch PDFs from URLs, you need to install the necessary dependencies. Open a terminal in your project directory and run the following commands:

npm install pdf-lib axios

Step 3 — Modifying the PdfModifier Component

Now, you’ll dive into the code to understand each part of the PdfModifier component.

In the template section, add a button with a click event that will trigger the handleButtonClick method when clicked:

<template>
	<div>
		<button @click="handleButtonClick">
			Add Text and Download PDF
		</button>
	</div>
</template>

In the script section, import the necessary modules from the pdf-lib and axios libraries. Then, define the PdfModifier Vue component and initialize the pdfUrl data property with the URL of the PDF you want to modify:

<script>
import { degrees, PDFDocument, rgb, StandardFonts } from 'pdf-lib';
import axios from 'axios';

export default {
  name: 'PdfModifier',
  data() {
      return {
         pdfUrl: 'https://pdf-lib.js.org/assets/with_update_sections.pdf', // URL to the PDF with update sections
      };
  },
  methods: {
    // ... (explained below)
  },
};
</script>

The modifyPdf method fetches the existing PDF from the specified URL using axios. It then loads the PDF using pdf-lib’s PDFDocument.load method. After embedding the Helvetica font, access the first page of the PDF. Using the drawText method, add text to the page, setting style properties like color, size, and rotation. The modified PDF is then saved to the pdfBytes variable using pdf-lib’s save method, and finally, it’s returned:

async modifyPdf() {
  const url = this.pdfUrl;

  // Fetch the PDF using axios.
  const response = await axios.get(url, { responseType: 'arraybuffer' });
  const existingPdfBytes = response.data;

  // Load the PDF using pdf-lib.
  const pdfDoc = await PDFDocument.load(existingPdfBytes);
  const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica);

  // Modify the PDF.
  const firstPage = pdfDoc.getPages()[0];
  const { height } = firstPage.getSize();
  firstPage.drawText('This text was added with JavaScript!', {
    x: 5,
    y: height / 2 + 300,
    size: 50,
    font: helveticaFont,
    color: rgb(0.95, 0.1, 0.1),
    rotate: degrees(-45),
  });

  // Save the modified PDF to a variable (`pdfBytes`).
  const pdfBytes = await pdfDoc.save();

  // Now, you can use `pdfBytes` for any additional operations (e.g. downloading the modified PDF).
  return pdfBytes;
},

The handleButtonClick method is called when the button is clicked. It invokes the modifyPdf method to modify the PDF and obtain the pdfBytes containing the modified PDF. It then calls the downloadPdf method to initiate the download:

async handleButtonClick() {
  try {
    const pdfBytes = await this.modifyPdf();
    this.downloadPdf(pdfBytes);
  } catch (error) {
    console.error('Error modifying PDF:', error);
  }
},

The downloadPdf method converts the pdfBytes into a blob with the application/pdf type. It creates a temporary anchor link with the blob’s URL and sets the download attribute to specify the file name. Clicking the anchor element initiates the download of the modified PDF:

downloadPdf(pdfBytes) {
  const blob = new Blob([pdfBytes], { type: 'application/pdf' });
  const link = document.createElement('a');
  link.href = URL.createObjectURL(blob);
  link.download = 'modified_pdf.pdf';
  link.click();
},

Step 4 — Running the Application

After setting up the project, run the application:

npm run serve

Click the Add Text and Download PDF button to modify the PDF and download the annotated version.

Leveraging PSPDFKit’s Vue.js PDF Annotation Library

When it comes to PDF annotations in a Vue.js application, you might wonder about options beyond pdf-lib. This is where PSPDFKit’s Vue.js PDF annotation library comes into play, providing a robust set of features:

  • 17 annotation types — PSPDFKit offers a wide range of annotation types, with even more on the horizon.
  • Customization — Tailor annotations to your needs by adjusting colors, shapes, and sizes.
  • Cross-device syncing — With server deployment, annotations can be synced seamlessly across multiple devices.
  • Real-time comment threads — Enable real-time conversations within documents, fostering collaboration (requires server deployment).
  • Customizable tooltips — Design tooltips to suit your specific requirements.
  • Framework support — PSPDFKit supports various web frameworks, eliminating the need to write APIs for different programming languages.

To explore the full capabilities of PSPDFKit and how it can enhance your project, take a look at our demo.

Integrating PSPDFKit with Vue.js

Now you’ll learn how to integrate PSPDFKit into your Vue.js project.

  1. Create a new Vue.js project for PSPDFKit integration:

vue create pspdfkit-vue-project

This will ask some configuration questions.

  1. Select Default (Vue 3) ([Vue 3] babel, eslint) from the list, and change the directory to pspdfkit-vue-project:

cd pspdfkit-vue-project

Adding PSPDFKit

  1. Install pspdfkit as a dependency with npm:

npm install pspdfkit
  1. Now, you can start building your Vue.js project. First, create a js directory under the public directory. Go to your terminal and run:

mkdir -p public/js
  1. Copy the PSPDFKit for Web library assets to the public/js directory:

cp -R ./node_modules/pspdfkit/dist/pspdfkit-lib public/js/pspdfkit-lib

This will copy the pspdfkit-lib directory from within node_modules/ into the public/js/ directory to make it available to the SDK at runtime.

Displaying the PDF

  1. Add the PDF document you want to display to the public directory. You can use our demo document as an example.

  2. Add a component wrapper for the PSPDFKit library and save it as src/components/PSPDFKitContainer.vue:

// src/components/PSPDFKitContainer.vue

<template>
	<div class="pdf-container"></div>
</template>

<script>
import PSPDFKit from 'pspdfkit';

export default {
	name: 'PSPDFKit',
	/**
	 * The component receives the `pdfFile` prop, which is of type `String` and is required.
	 */
	props: {
		pdfFile: {
			type: String,
			required: true,
		},
	},
	/**
	 * We wait until the template has been rendered to load the document into the library.
	 */
	mounted() {
		this.loadPSPDFKit().then((instance) => {
			this.$emit('loaded', instance);
		});
	},
	/**
	 * We watch for `pdfFile` prop changes and trigger unloading and loading when there's a new document to load.
	 */
	watch: {
		pdfFile(val) {
			if (val) {
				this.loadPSPDFKit();
			}
		},
	},
	/**
	 * Our component has the `loadPSPDFKit` method. This unloads and cleans up the component and triggers document loading.
	 */
	methods: {
		async loadPSPDFKit() {
			PSPDFKit.unload('.pdf-container');
			return PSPDFKit.load({
				// To access the `pdfFile` from props, use `this` keyword.
				document: this.pdfFile,
				container: '.pdf-container',
			});
		},
	},

	/**
	 * Clean up when the component is unmounted so it's ready to load another document (not needed in this example).
	 */
	beforeUnmount() {
		PSPDFKit.unload('.pdf-container');
	},
};
</script>

<style scoped>
.pdf-container {
	height: 100vh;
}
</style>

Here’s what’s happening in your component:

  • The template section is rendering a div with the pdf-container class. This will help you declaratively bind the rendered DOM to the underlying component instance’s data.

  • The script section is defining a Vue.js instance named PSPDFKit and creating methods for mounting, loading, and unloading PDF files into the pdf-container.

  • The style section is defining the height of the container.

  1. Now, replace the contents of src/App.vue with the following:

// src/App.vue

<template>
	<div id="app">
		<label for="file-upload" class="custom-file-upload">
			Open PDF
		</label>
		<input
			id="file-upload"
			type="file"
			@change="openDocument"
			class="btn"
		/>
		<PSPDFKitContainer :pdfFile="pdfFile" @loaded="handleLoaded" />
	</div>
</template>

<script>
import PSPDFKitContainer from '@/components/PSPDFKitContainer';

export default {
	data() {
		return {
			pdfFile: this.pdfFile || '/example.pdf',
		};
	},
	/**
	 * Render the `PSPDFKitContainer` component.
	 */
	components: {
		PSPDFKitContainer,
	},
	/**
	 * Our component has two methods — one to check when the document is loaded, and the other to open the document.
	 */
	methods: {
		handleLoaded(instance) {
			console.log('PSPDFKit has loaded: ', instance);
			// Do something.
		},

		openDocument() {
			// To access the Vue instance data properties, use `this` keyword.
			if (this.pdfFile) {
				window.URL.revokeObjectURL(this.pdfFile);
			}
			this.pdfFile = window.URL.createObjectURL(
				event.target.files[0],
			);
		},
	},
};
</script>

<style>
#app {
	text-align: center;
	color: #2c3e50;
}

body {
	margin: 0;
}

input[type='file'] {
	display: none;
}

.custom-file-upload {
	border: 1px solid #ccc;
	border-radius: 4px;
	display: inline-block;
	padding: 6px 12px;
	cursor: pointer;
	background: #4a8fed;
	padding: 10px;
	color: #fff;
	font: inherit;
	font-size: 16px;
	font-weight: bold;
}
</style>
  • In the template section, you have a file upload input and the PSPDFKitContainer component.

Information

Vue.js uses directives to handle some types of functionality. For the input field, you’re using the v-on directive to attach an event listener to the element. In your case, it’s the change event. There’s a shortcut to v-on that removes the keyword and uses an @ symbol instead.

v-on:change="openDocument"
v-on:loaded="handleLoaded"
// or
@change="openDocument"
@loaded="handleLoaded"

Similar to the input field, for the PSPDFKitContainer component, you’re using the v-bind directive to bind the pdfFile property to the pdfFile property of the component and attaching an event listener for the loaded event:

<PSPDFKitContainer :pdfFile="pdfFile" @loaded="handleLoaded" />
  • In the script section, you can see the implementation of the handleLoaded and openDocument methods. There’s also a data function that returns the pdfFile property.

The data keeps track of reactive state within the current component. It’s always a function and returns an object. The object’s top-level properties are exposed via the component instance.

  • In the style section, there are styles for custom file input, and there are some general styles for the app.

  1. Start the app:

npm run serve

You can see the application running on localhost:8080.

In the demo application, you can open different PDF files by clicking the Open PDF button. You can add signatures, annotations, stamps, and more.

Information

All the finished code is available on GitHub. 😎 You can find the example code for Vue 2 in the vue-2 branch.

Adding Text Annotations to a PDF Using PSPDFKit and Vue.js

PSPDFKit provides the PSPDFKit.Annotations.TextAnnotation class to create text annotations and customize them with various properties such as font, color, size, alignment, and position.

Inside the PSPDFKitContainer.vue component, add the createTextAnnotation method to add text annotations to the PDF:

<script>
import PSPDFKit from 'pspdfkit';

export default {
	// ... Other component options.
	methods: {
		// ... Other methods.
		async createTextAnnotation({ PSPDFKit, instance }) {
			const annotation = new PSPDFKit.Annotations.TextAnnotation({
				pageIndex: 0, // Specify the page number for the annotation.
				text: {
					format: 'plain',
					value: 'Welcome to PSPDFKit', // Text to embed.
				},
				font: 'Helvetica',
				isBold: true,
				horizontalAlign: 'left', // Align the annotation to the center of the bounding box.
				boundingBox: new PSPDFKit.Geometry.Rect({
					// Position of the annotation.
					left: 50,
					top: 200,
					width: 100,
					height: 80,
				}),
				fontColor: PSPDFKit.Color.BLUE, // Color of the text.
			});

			// Attach this annotation to your PDF.
			const createdAnnotation = await instance.create(annotation);
			return createdAnnotation;
		},
	},
};
</script>

In the mounted lifecycle hook of the PSPDFKitContainer.vue component, call the createTextAnnotation method after loading PSPDFKit to add the text annotation:

<script>
import PSPDFKit from 'pspdfkit';

export default {
	// ... Other component options.
	mounted() {
		this.loadPSPDFKit().then((instance) => {
			this.$emit('loaded', instance);
			this.createTextAnnotation({ PSPDFKit, instance }); // Call the method to add the text annotation.
		});
	},
	// ... Other methods.
};
</script>

When you run the application, you can see the text annotation added to the PDF.

Adding Ink Annotations to a PDF Using PSPDFKit and Vue.js

To insert ink annotations into your Vue.js app using PSPDFKit, follow the steps below.

  1. Inside the PSPDFKitContainer.vue file, add the following createInkAnnotation function:

// PSPDFKitContainer.vue

<script>
import PSPDFKit from 'pspdfkit';

export default {
	// ... (other component options)

	methods: {
		async createInkAnnotation({ x1, y1, x2, y2 }) {
			// Extract the `List`, `DrawingPoint`, `Rect`, and `InkAnnotation` properties from PSPDFKit.
			// These are needed to render annotations onto the screen.
			const { List } = PSPDFKit.Immutable;
			const { DrawingPoint, Rect } = PSPDFKit.Geometry;
			const { InkAnnotation } = PSPDFKit.Annotations;

			// Create your ink annotation.
			const annotation = new InkAnnotation({
				pageIndex: 0,
				boundingBox: new Rect({ width: 400, height: 100 }), // position of annotation
				strokeColor: new PSPDFKit.Color({ r: 255, g: 0, b: 255 }), // color of stroke
				lines: List([
					// Coordinates of stroke.
					List([
						new DrawingPoint({ x: x1, y: y1 }),
						new DrawingPoint({ x: x2, y: y2 }),
					]),
				]),
			});

			const instance = await this.loadPSPDFKit();
			// Attach stroke to annotation:
			const createdAnnotation = await instance.create(annotation);
			return createdAnnotation;
		},
		async addInkAnnotation() {
			await this.createInkAnnotation({
				x1: 5, // Starting x-coordinate.
				y1: 5, // Starting y-coordinate.
				x2: 100, // Ending x-coordinate.
				y2: 100, // Ending y-coordinate.
			});
		},
	},

	// ... (other component options)
};
</script>
  1. Call the addInkAnnotation method inside the mounted hook of the PSPDFKitContainer.vue component:

<script>
import PSPDFKit from 'pspdfkit';

export default {
	// ... (other component options)

	mounted() {
		PSPDFKit.load({
			document: this.pdfFile,
			container: '.pdf-container',
		}).then((instance) => {
			this.$emit('loaded', instance);
			this.addInkAnnotation(instance); // Add the ink annotation after PSPDFKit is loaded.
		});
	},

	// ... (other component options)
};
</script>

Conclusion

In this tutorial, you learned how to use the pdf-lib library with Vue.js to add text annotations to existing PDF documents and download the modified PDF. You also explored integrating PSPDFKit, a powerful PDF SDK, into a Vue.js project for advanced PDF annotation capabilities, including text and ink annotations. With these tools, you can create interactive and collaborative PDF annotation features in your Vue.js applications.

If you’re interested in exploring PSPDFKit further, you can request a free trial of our SDK or visit our demo page to experience the capabilities of our product firsthand.

Related Products
Share Post
Free 60-Day Trial Try PSPDFKit in your app today.
Free Trial

Related Articles

Explore more
PRODUCTS  |  Web • Releases • Components

PSPDFKit for Web 2024.3 Features New Stamps and Signing UI, Export to Office Formats, and More

PRODUCTS  |  Web • Releases • Components

PSPDFKit for Web 2024.2 Features New Unified UI Icons, Shadow DOM, and Tab Ordering

PRODUCTS  |  Web

Now Available for Public Preview: New Document Authoring Experience Provides Glimpse into the Future of Editing