Subtitle:
A React-based custom plugin for rendering elements at precise coordinates on PDF pages
Core Idea:
The Coordinate Marker Plugin leverages React PDF Viewer's plugin architecture to render React components at exact coordinates within PDF pages, enabling annotation, highlighting, or interactive elements at specific positions in documents.
Key Principles:
- React-Centric Implementation:
- Uses React components and portals instead of direct DOM manipulation
- Coordinate-Based Positioning:
- Places elements at precise (x,y) coordinates relative to PDF page dimensions
- Page-Focused Navigation:
- Automatically navigates to specific pages and scrolls to relevant coordinates
Why It Matters:
- Enhanced Document Interaction:
- Enables applications to highlight or annotate specific parts of documents
- Context-Aware Navigation:
- Helps users quickly locate relevant information within large documents
- Interactive Document Experiences:
- Facilitates creation of interactive guides, tutorials, or reference materials
How to Implement:
- Define Plugin Structure:
- Create a TypeScript interface for the plugin and its data types
- Implement a store for managing coordinates and rendering state
- Use Proper Rendering Methods:
- Leverage the
renderPageLayer
hook for consistent rendering - Ensure components respect the PDF scaling and rotation
- Leverage the
- Handle Coordinate Rendering:
- Position elements using percentage-based positioning for scale independence
- Update positions when document scale or rotation changes
Example:
- Scenario:
- Creating a custom plugin that renders markers at specific coordinates in a PDF document
- Application:
import * as React from 'react';
import { createStore, Plugin, PluginFunctions, PluginOnPageRender, PluginRenderPageLayer } from '@react-pdf-viewer/core';
// Define marker data type
interface Coordinate {
x0: number;
y0: number;
x1: number;
y1: number;
className?: string;
}
// Define store properties
interface StoreProps {
coordinates: Coordinate[];
scale: number;
rotation: number;
isMatch: boolean;
jumpToDestination?: (destination: {
pageIndex: number;
bottomOffset?: (viewportWidth: number, viewportHeight: number) => number;
leftOffset?: (viewportWidth: number, viewportHeight: number) => number;
scaleTo?: number;
}) => void;
}
// Define plugin interface
interface CoordinateMarkerPlugin extends Plugin {
setCoordinates: (page: number, coordinates: Coordinate[], isMatch: boolean) => void;
jumpToCoordinate: (page: number, coordinate: Coordinate) => void;
}
interface CoordinateMarkerPluginOptions {
page?: number;
coordinates?: Coordinate[];
isMatch?: boolean;
}
// Create the marker component that will be rendered on each page
const CoordinateMarkers: React.FC<{
pageIndex: number;
coordinates: Coordinate[];
isMatch: boolean;
}> = ({ pageIndex, coordinates, isMatch }) => {
if (!coordinates || coordinates.length === 0) {
return null;
}
return (
<>
{coordinates.map((coordinate, index) => (
<div
key={`${pageIndex}-marker-${index}`}
className={`rpv-coordinate-marker ${coordinate.className || ''}`}
style={{
position: 'absolute',
left: `${(coordinate.x0 * 100) / 612}%`,
top: `${(coordinate.y0 * 100) / 792}%`,
width: `${((coordinate.x1 - coordinate.x0) * 100)/612}%`,
height: `${((coordinate.x1 - coordinate.x0) * 100)/792}%`,
border: `2px solid ${isMatch ? "rgba(0, 255, 0, 0.5)" : "rgba(255, 0, 0, 0.5)"}`,
pointerEvents: "none"
}}
>
</div>
))}
</>
);
};
// Create the plugin
const CoordinateMarkerPlugin = (options: CoordinateMarketPluginOptions = {}): CoordinateMarkerPlugin => {
const { page = -1, coordinates = [], isMatch = true } = options;
const store = React.useMemo(() => createStore<StoreProps>({
coordinates,
pageIndex: page,
isMatch,
scale: 1,
rotation: 0,
}), []);
const jumpToCoordinate = (page: number, coordinate: Coordinate) => {
const jumpToDestination = store.get('jumpToDestination');
if (!jumpToDestination) {
return;
}
jumpToDestination({
pageIndex: page,
bottomOffset: (_, viewportHeight) => viewportHeight - coordinate.y0,
leftOffset: (_, __) => coordinate.x0,
});
};
return {
install: (pluginFunctions: PluginFunctions) => {
store.update('jumpToDestination', pluginFunctions.jumpToDestination);
},
onPageRender: (e: PluginOnPageRender) => {
store.update('scale', e.scale);
store.update('rotation', e.rotation);
},
// Use renderPageLayer instead of renderViewer with portals
renderPageLayer: (props: PluginRenderPageLayer) => (
if (props.pageIndex !== store.get('pageIndex')) { return <></>; }
<CoordinateMarkers
pageIndex={props.pageIndex}
coordinates={store.get('coordinates')}
isMatch={store.get('isMatch')}
/>
),
setCoordinates: (page: number, coordinates: Coordinate[], isMatch: bool) => {
if (page < 0) {
return;
}
store.update('coordinates', coordinates);
store.update('pageIndex', page);
store.update('isMatch', isMatch);
// If there are coordinates, jump to the first one
if (coordinates.length > 0) {
jumpToCoordinate(page, coordinates[0]);
}
},
jumpToCoordinate,
};
};
export default coordinateMarkerPlugin;
export type { Coordinate, CoordinateMarkerPlugin, CoordinateMarkerPluginOptions };
- Result:
- A fully React-based plugin that renders markers at specific coordinates, with automatic navigation and proper scaling
Connections:
- Related Concepts:
- React PDF Viewer Custom Plugins: Framework for building custom PDF viewer extensions
- React PDF Viewer Plugin Lifecycle Methods: Events and hooks utilized by the plugin
- React PDF Viewer createStore Function: State management approach used in the plugin
- FRP Policy Checker: Project this was develop under
- useMemo: React optimization hook used for efficient store creation
- React Store Pattern: Centralized state management for coordinating plugin behavior
- Broader Concepts:
- PDF Annotation Techniques: Methods for adding interactive elements to documents- Document Coordinate Systems: Understanding how coordinates map to document dimensions
References:
- Primary Source:
- React PDF Viewer Documentation: Plugin Development Guide
- Additional Resources:
- React Portal Documentation: https://reactjs.org/docs/portals.html
- PDF.js Documentation: Page coordinate systems
Tags:
#react #pdf #coordinates #markers #plugin #portals #annotation #custom-plugin
Connections:
Sources:
- From: Version1