PDF.js text highlighting: Display annotations in JavaScript
Table of contents
- PDF.js — Read existing highlight annotations and display their bounding rectangles as HTML overlays. The overlays don’t modify the PDF.
- Nutrient Web SDK — Create highlight annotations programmatically and export an annotated PDF for download or storage.
PDF.js powers Firefox’s full PDF viewer, which supports creating highlights and saving annotated PDFs(opens in a new tab). That editor is separate from the minimal canvas example in this tutorial: Calling getAnnotations() reads annotations that already exist.
Downloading the PDF.js library
Download(opens in a new tab) the PDF.js library as a ZIP file, clone the repository using Git, or install it via npm:
npm install pdfjs-dist@6.3.289This example was tested with PDF.js 6.3.289 in Chromium.
If you downloaded the ZIP file, extract it and copy the pdf.mjs and pdf.worker.mjs files from the build/ folder to your project directory. If you installed via npm, copy them from node_modules/pdfjs-dist/build/:
cp node_modules/pdfjs-dist/build/pdf.mjs node_modules/pdfjs-dist/build/pdf.worker.mjs .Loading and rendering PDF documents
Serve these files over HTTP using a local server, such as npx serve ., and open the address it prints. Browser module imports and PDF loading won’t work reliably from a file:// URL.
Save this example as index.html alongside the PDF.js files. Download the PDF.js demo document as annotation.pdf, or replace that file name with your PDF document path. Before opening the page, add the renderHighlightAnnotations() function from the next section to the same script block:
<!doctype html><html> <head> <title>PDF.js Highlight Annotations</title> <style> body { margin: 0; padding: 20px; position: relative; } #canvas { border: 1px solid #ccc; } </style> </head> <body> <!-- Canvas to place the PDF. --> <canvas id="canvas"></canvas>
<script type="module"> import * as pdfjsLib from './pdf.mjs';
// Get the canvas element. const canvas = document.getElementById('canvas');
// Get the PDF file URL. const pdfUrl = 'annotation.pdf'; // Replace with the URL of your PDF document.
// Configure the PDF.js worker source. pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.mjs';
// Load the PDF file using PDF.js. pdfjsLib.getDocument({ url: pdfUrl }).promise.then(function (pdfDoc) { // Get the first page of the PDF file. return pdfDoc.getPage(1).then(function (page) { const viewport = page.getViewport({ scale: 1 });
// Set the canvas dimensions to match the PDF page size. canvas.width = viewport.width; canvas.height = viewport.height;
// Set the canvas rendering context. const ctx = canvas.getContext('2d');
const renderContext = { canvasContext: ctx, viewport: viewport, // Draw annotations separately in the overlay below. annotationMode: pdfjsLib.AnnotationMode.DISABLE, };
// Render the PDF page to the canvas. return page.render(renderContext).promise.then(function () { console.log('Rendering complete'); // Call the function to render highlight annotations after the PDF page is rendered. return renderHighlightAnnotations(page, viewport); }); }); }).catch(function (error) { console.error( 'Unable to load or render the PDF. Check the PDF and worker URLs.', error, ); }); </script> </body></html>Displaying existing highlights with PDF.js
Use the getAnnotations() method to retrieve existing annotations from the PDF page. Add the following function inside your <script type="module"> block:
function renderHighlightAnnotations(page, viewport) { // Position the overlay over the canvas at its current size and location. const parent = canvas.parentNode; if (getComputedStyle(parent).position === 'static') { parent.style.position = 'relative'; } const overlay = document.createElement('div'); overlay.style.position = 'absolute'; overlay.style.left = canvas.offsetLeft + canvas.clientLeft + 'px'; overlay.style.top = canvas.offsetTop + canvas.clientTop + 'px'; overlay.style.width = viewport.width + 'px'; overlay.style.height = viewport.height + 'px'; overlay.style.pointerEvents = 'none'; parent.appendChild(overlay);
return page.getAnnotations().then(function (annotations) { annotations.forEach(function (annotation) { if (annotation.subtype === 'Highlight') { // Convert the PDF user-space rectangle to viewport (canvas) // coordinates. This handles the bottom-up Y axis, the current // scale, and any page rotation in one step. const [left, bottom, right, top] = annotation.rect; const [x1, y1] = viewport.convertToViewportPoint(left, bottom); const [x2, y2] = viewport.convertToViewportPoint(right, top); const highlight = document.createElement('div'); highlight.style.position = 'absolute'; highlight.style.left = Math.min(x1, x2) + 'px'; highlight.style.top = Math.min(y1, y2) + 'px'; highlight.style.width = Math.abs(x2 - x1) + 'px'; highlight.style.height = Math.abs(y2 - y1) + 'px'; highlight.style.backgroundColor = 'yellow'; highlight.style.opacity = '0.5'; highlight.style.pointerEvents = 'none'; overlay.appendChild(highlight); } }); });}The function calls getAnnotations() on the first page and creates a yellow overlay for each annotation whose subtype is 'Highlight'. viewport.convertToViewportPoint() converts two opposite corners of the PDF rectangle to canvas coordinates using the viewport’s current scale and rotation. Setting annotationMode to DISABLE in the canvas render prevents PDF.js from also painting the annotations underneath the overlays.
This is a simplified display example. It uses each annotation’s bounding rectangle, so a highlight spanning several lines can cover the gaps between them. It also replaces the original color with yellow and hides other annotation appearances. For the document’s original appearance, remove annotationMode: pdfjsLib.AnnotationMode.DISABLE and the call to renderHighlightAnnotations().
If you change the scale, rotation, or layout, remove the old overlay and rebuild it after rendering the page again. The example doesn’t provide text selection, create new PDF annotations, or save changes.
Nutrient Web SDK
Nutrient provides a commercial JavaScript PDF viewer library for web applications. Nutrient Web SDK supports viewing, annotating, editing, and signing PDFs directly in the browser. Nutrient Web SDK is a separate implementation from PDF.js. Use a new project folder for the Nutrient example, or replace the earlier PDF.js HTML with the Nutrient HTML shown here.
Requirements
- Node.js(opens in a new tab)
- A package manager compatible with npm
Adding Nutrient to your project
Install the @nutrient-sdk/viewer package via npm, or download the SDK manually:
npm i @nutrient-sdk/viewerpnpm add @nutrient-sdk/vieweryarn add @nutrient-sdk/viewerTo run Nutrient Web SDK in the browser, copy the required library files (artifacts) to your assets folder:
cp -R ./node_modules/@nutrient-sdk/viewer/dist/ ./assets/Make sure your assets/ folder contains:
nutrient-viewer.js(entry point)- A
nutrient-viewer-lib/directory with the required runtime assets
Integrating into your project
Add the PDF document you want to display (e.g.
document.pdf) to the root of your project. You can use the Nutrient demo document as an example.Add an empty
<div>element to your HTML where the viewer will mount:<div id="nutrient" style="height: 100vh;"></div>Save the following complete example as
index.htmlin your project root. It imports the SDK and initializes it usingNutrientViewer.load():
<!doctype html><html lang="en"> <head> <title>Nutrient PDF Viewer</title> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> </head> <body> <div id="nutrient" style="height: 100vh;"></div>
<script type="module"> import './assets/nutrient-viewer.js';
const baseUrl = `${window.location.protocol}//${window.location.host}/assets/`;
NutrientViewer.load({ container: '#nutrient', document: 'document.pdf', baseUrl, }) .then((instance) => { console.log('Nutrient Viewer loaded', instance); }) .catch((error) => { console.error(error.message); }); </script> </body></html> 
Creating highlight annotations with Nutrient
The NutrientViewer.Annotations.HighlightAnnotation constructor creates a highlight annotation object with a page index and rectangles to highlight. Replace the earlier NutrientViewer.load() block with the following code so the viewer is initialized only once:
NutrientViewer.load({ container: '#nutrient', document: 'document.pdf', baseUrl,}) .then(async function (instance) { try { console.log('Nutrient loaded', instance); const rects = NutrientViewer.Immutable.List([ new NutrientViewer.Geometry.Rect({ left: 10, top: 120, width: 200, height: 10, }), new NutrientViewer.Geometry.Rect({ left: 10, top: 150, width: 200, height: 10, }), ]); const annotation = new NutrientViewer.Annotations.HighlightAnnotation( { pageIndex: 0, rects: rects, boundingBox: NutrientViewer.Geometry.Rect.union(rects), }, ); await instance.create(annotation); console.log('Highlight annotation added successfully.'); } catch (error) { console.error('Error adding highlight annotation:', error.message); } }) .catch(function (error) { console.error('NutrientViewer failed to load:', error.message); });The rects list defines two areas on the first page (pageIndex: 0). Adjust these coordinates to match the text in your document. Calling instance.create(annotation) adds the annotation to the open viewer instance.
To export the new highlight, call await instance.save() followed by await instance.exportPDF() inside the same async callback, after await instance.create(annotation). Handle the returned ArrayBuffer to download or upload the PDF. Exporting doesn’t overwrite the PDF at its original URL. See saving annotations into a PDF and exporting a PDF to an ArrayBuffer for the complete persistence workflow.
Serving your website
Install the
servepackage:Terminal window npm install --global serveServe the contents of the current directory:
Terminal window serve -l 8080 .Navigate to
http://localhost:8080to view the website.

Add highlighting to your application
Use Nutrient Web SDK to add highlights at your document’s text coordinates and export the annotated PDF for download or storage.
Try highlighting a PDF in the Nutrient demo, or contact Sales to discuss adding annotation and export features to your application.
FAQ
No. HTML overlays belong to the webpage and don’t modify the PDF. To include highlights in the downloaded document, create PDF annotations and export the updated file.
Install the @nutrient-sdk/viewer package via npm, copy the library assets to your project, and initialize the viewer with NutrientViewer.load().
Yes. Construct a NutrientViewer.Annotations.HighlightAnnotation and pass it to instance.create(). Save and export the document when you need a PDF file containing the annotation.
Yes. Its full viewer supports creating and saving highlights. In a custom canvas integration, getAnnotations() reads existing annotations; drawing an HTML overlay doesn’t create or save a PDF annotation.