Highlight with Bbox Coordinates Programmatically
Scenario
Section titled “Scenario”When the PDF document is loaded, the system programmatically highlights specific regions using bounding box coordinates:
- Highlights multiple regions on page 2 (second page) with exact coordinates
- Highlights additional regions on page 3 (third page)
- Each highlighted region uses a different color to distinguish different areas of interest
- Automatically scales highlights when users zoom in/out
The bounding box coordinates define precise rectangular areas on the PDF pages, making it ideal for:
- Highlighting table cells, form fields, or specific document sections
- Marking regions detected by OCR or document analysis tools
- Displaying annotations from external data sources with precise positioning
- Visualizing search results or text/table extraction
Understanding Bounding Box Coordinates
Section titled “Understanding Bounding Box Coordinates”Bounding box coordinates define rectangular regions on PDF pages:
- pageIndex: Zero-based page number (0 for first page, 1 for second page, etc.)
- x: Horizontal distance from the left edge of the page in points
- y: Vertical distance from the top edge of the page in points
- width: Width of the highlighted region in points
- height: Height of the highlighted region in points
- highlightColor: Color of the highlight (supports rgba, hex, or named CSS colors)
- id: Optional unique identifier for the highlight
Coordinate System:
- Origin (0, 0) is at the top-left corner of each page
- Coordinates are in PDF points, where 1 point = 1/72 inch
- Coordinates are specified at 100% zoom and automatically scaled by the viewer
- The scale factor is calculated as
scale / 100(e.g., 150% zoom = 1.5 scale factor)
What to Use
Section titled “What to Use”The useElementPageContext hook provides the updateElement function to programmatically add overlay elements to PDF pages. This function accepts a page number and a callback that returns React elements positioned using bounding box coordinates. The callback receives the current scale factor, allowing highlights to automatically adjust when users zoom in or out.
Here are the key concepts we’re using for this example
| Name | Objective |
|---|---|
updateElement | Add custom overlay elements to specific pages based on bbox coordinates |
Bounding Box (bbox) | Define rectangular regions with x, y, width, and height in PDF points |
| Scale Factor | Automatically adjust highlight positioning for different zoom levels |
import { RPConfig, RPProvider, RPDefaultLayout, RPPages, useElementPageContext,} from "@pdf-viewer/react";import { useEffect } from "react";
// Define highlight data structureconst highlightDataList = [ { id: "highlight-1", pageIndex: 1, // Second page (zero-based) bbox: { x: 100, y: 200, width: 300, height: 50 }, highlightColor: "rgba(255, 179, 0, 0.5)", // Orange }, { id: "highlight-2", pageIndex: 1, bbox: { x: 200, y: 300, width: 300, height: 50 }, highlightColor: "rgba(0, 0, 255, 0.5)", // Blue }, { id: "highlight-3", pageIndex: 2, // Third page (zero-based) bbox: { x: 150, y: 300, width: 250, height: 100 }, highlightColor: "rgba(255, 0, 255, 0.5)", // Magenta },];
// Helper function to group highlights by pagefunction groupByPageIndex(highlights) { return highlights.reduce((acc, item) => { const pageIndex = item.pageIndex; if (!acc[pageIndex]) { acc[pageIndex] = []; } acc[pageIndex].push(item); return acc; }, {});}
const BboxHighlightLayer = () => { const { updateElement } = useElementPageContext();
useEffect(() => { // Group highlights by page for efficient rendering const highlightsByPage = groupByPageIndex(highlightDataList);
// Iterate through each page that has highlights for (const pageIndexStr in highlightsByPage) { const pageIndex = Number(pageIndexStr); const pageHighlights = highlightsByPage[pageIndex];
// Update the specified page with highlight overlays // Note: updateElement uses 1-based page numbers updateElement( pageIndex + 1, (_prevElements, _dimension, _rotate, scale) => { // Calculate scale factor for proper positioning at any zoom level const scaleFactor = scale / 100;
// Create a highlight overlay for each bbox on this page return pageHighlights.map((highlight, index) => { const { bbox, highlightColor, id } = highlight;
return ( <div key={id || `highlight-${pageIndex}-${index}`} style={{ position: "absolute", // Apply scale factor to bbox coordinates left: bbox.x * scaleFactor, top: bbox.y * scaleFactor, width: bbox.width * scaleFactor, height: bbox.height * scaleFactor, backgroundColor: highlightColor, pointerEvents: "none", }} /> ); }); } ); }
}, [updateElement]);
return null;};
export const AppPdfViewer = () => { return ( <> <RPConfig licenseKey="YOUR_LICENSE_KEY"> <RPProvider src="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"> <BboxHighlightLayer /> <RPDefaultLayout> <RPPages /> </RPDefaultLayout> </RPProvider> </RPConfig> </> )}import { RPConfig, RPProvider, RPDefaultLayout, RPPages, useElementPageContext,} from "@pdf-viewer/react";import { useEffect, type FC } from "react";
// Define interfaces for type safetyinterface BoundingBox { x: number; y: number; width: number; height: number;}
interface HighlightData { pageIndex: number; // Zero-based page index bbox: BoundingBox; highlightColor: string; id?: string;}
// Define highlight data structureconst highlightDataList: HighlightData[] = [ { id: "highlight-1", pageIndex: 1, // Second page (zero-based) bbox: { x: 100, y: 200, width: 300, height: 50 }, highlightColor: "rgba(255, 179, 0, 0.5)", // Orange }, { id: "highlight-2", pageIndex: 1, bbox: { x: 200, y: 300, width: 300, height: 50 }, highlightColor: "rgba(0, 0, 255, 0.5)", // Blue }, { id: "highlight-3", pageIndex: 2, // Third page (zero-based) bbox: { x: 150, y: 300, width: 250, height: 100 }, highlightColor: "rgba(255, 0, 255, 0.5)", // Magenta },];
// Helper function to group highlights by pagefunction groupByPageIndex( highlights: HighlightData[]): Record<number, HighlightData[]> { return highlights.reduce( (acc, item) => { const pageIndex = item.pageIndex; if (!acc[pageIndex]) { acc[pageIndex] = []; } acc[pageIndex].push(item); return acc; }, {} as Record<number, HighlightData[]> );}
const BboxHighlightLayer: FC = () => { const { updateElement } = useElementPageContext();
useEffect(() => { // Group highlights by page for efficient rendering const highlightsByPage = groupByPageIndex(highlightDataList);
// Iterate through each page that has highlights for (const pageIndexStr in highlightsByPage) { const pageIndex = Number(pageIndexStr); const pageHighlights = highlightsByPage[pageIndex];
// Update the specified page with highlight overlays // Note: updateElement uses 1-based page numbers updateElement( pageIndex + 1, (_prevElements, _dimension, _rotate, scale) => { // Calculate scale factor for proper positioning at any zoom level const scaleFactor = scale / 100;
// Create a highlight overlay for each bbox on this page return pageHighlights.map((highlight, index) => { const { bbox, highlightColor, id } = highlight;
return ( <div key={id || `highlight-${pageIndex}-${index}`} style={{ position: "absolute", // Apply scale factor to bbox coordinates left: bbox.x * scaleFactor, top: bbox.y * scaleFactor, width: bbox.width * scaleFactor, height: bbox.height * scaleFactor, backgroundColor: highlightColor, pointerEvents: "none", }} /> ); }); } ); } }, [updateElement]);
return null;};
export const AppPdfViewer: FC = () => { return ( <> <RPConfig licenseKey="YOUR_LICENSE_KEY"> <RPProvider src="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"> <BboxHighlightLayer /> <RPDefaultLayout> <RPPages /> </RPDefaultLayout> </RPProvider> </RPConfig> </> );};Notes:
- The
updateElementfunction uses 1-based page numbers, so add 1 to the zero-basedpageIndex - The
scaleparameter fromupdateElementrepresents zoom percentage (100 = 100%, 150 = 150%) - Apply the scale factor to all bbox coordinates for proper positioning at different zoom levels
- Use
pointerEvents: "none"to prevent highlights from interfering with PDF interactions