Skip to content

QuocVietHa08/react-pdf-highlighter-plus

 
 

Repository files navigation

react-pdf-highlighter-plus

GitHub stars Node.js CI npm version npm downloads

A powerful React library for annotating PDF documents

Text highlights β€’ Area highlights β€’ Freetext notes β€’ Images & signatures β€’ Freehand drawing β€’ Shapes β€’ Search β€’ PDF export


Overview

react-pdf-highlighter-plus provides a highly customizable annotation experience for PDF documents in React applications. Built on PDF.js, it stores highlight positions in viewport-independent coordinates, making them portable across different screen sizes.

react-pdf-highlighter-plus internal architecture

Features

Feature Description
Text Highlights Select and highlight text passages, restyle them, and copy their text
Area Highlights Draw rectangular regions on PDFs and copy intersecting PDF text
Freetext Notes Draggable, editable sticky notes with custom styling and compact mode
Images & Signatures Upload images or draw signatures directly on PDFs
Freehand Drawing Draw freehand annotations with customizable stroke
Shapes Add rectangles, circles, and arrows with editable stroke style
PDF Search Search through all PDF text with next/previous navigation
PDF Export Export annotated PDF with all highlights embedded
Local PDF Worker Uses the packaged PDF.js worker by default
Light/Dark Theme Hue-preserving dark mode (OKLab recolor) β€” photos & colors stay readable
Zoom Support Buttons, pinch / ctrl+wheel zoom, position-independent data
Smooth Scroll Animated scroll-to-highlight (respects reduced-motion)
Deep Linking initialPage + onPageChange for ?page=N style navigation
Locate Text getTextPosition β€” turn any quote into a precise highlight (citations)
Read Aloud Text-to-speech that highlights & follows each sentence (recipe)
Fast Loading Progressive range loading, auth headers, document caching
Fully Customizable Exposed styling on all components

Quick Links

Resource Link
Live Demo View Demo
Documentation API Docs
NPM Package npm

Installation

npm install react-pdf-highlighter-plus

Import Styles

import "react-pdf-highlighter-plus/style/style.css";

PDF.js worker setup is handled by the package by default. The build copies the local pdf.worker.min.mjs into the package output, so most apps do not need to configure workerSrc manually.


Quick Start

Basic Setup

import {
  PdfLoader,
  PdfHighlighter,
  TextHighlight,
  AreaHighlight,
  useHighlightContainerContext,
} from "react-pdf-highlighter-plus";
import "react-pdf-highlighter-plus/style/style.css";

function App() {
  const [highlights, setHighlights] = useState([]);

  return (
    <PdfLoader document="https://example.com/document.pdf">
      {(pdfDocument) => (
        <PdfHighlighter
          pdfDocument={pdfDocument}
          highlights={highlights}
          enableAreaSelection={(e) => e.altKey}
        >
          <HighlightContainer />
        </PdfHighlighter>
      )}
    </PdfLoader>
  );
}

function HighlightContainer() {
  const { highlight, isScrolledTo } = useHighlightContainerContext();

  return highlight.type === "text" ? (
    <TextHighlight highlight={highlight} isScrolledTo={isScrolledTo} />
  ) : (
    <AreaHighlight highlight={highlight} isScrolledTo={isScrolledTo} />
  );
}

Highlight Types

1. Text Highlights

Select text in the PDF to create highlights.

<TextHighlight
  highlight={highlight}
  isScrolledTo={isScrolledTo}
  style={{ background: "rgba(255, 226, 143, 1)" }}
/>

Text and area highlight toolbars include a copy button. After copying, the icon changes to a check mark for 1.5 seconds.

Full Documentation β†’

2. Area Highlights

Hold Alt and drag to create rectangular highlights.

<PdfHighlighter
  enableAreaSelection={(event) => event.altKey}
  // ...
>

3. Freetext Notes

Create draggable, editable text annotations with customizable styling.

import { FreetextHighlight } from "react-pdf-highlighter-plus";

<PdfHighlighter
  enableFreetextCreation={() => freetextMode}
  onFreetextClick={(position) => {
    addHighlight({ type: "freetext", position, content: { text: "Note" } });
  }}
>

// In your highlight container:
<FreetextHighlight
  highlight={highlight}
  onChange={handlePositionChange}
  onTextChange={handleTextChange}
  onStyleChange={handleStyleChange}
  color="#333333"
  backgroundColor="#ffffc8"
  fontSize="14px"
/>

Features:

  • Drag to reposition
  • Click to edit text
  • Built-in style panel (colors, font size, font family)
  • Toolbar appears on hover

Full Documentation β†’

4. Images & Signatures

Upload images or draw signatures and place them on PDFs.

import { ImageHighlight, SignaturePad } from "react-pdf-highlighter-plus";

// Signature pad modal
<SignaturePad
  isOpen={isOpen}
  onComplete={(dataUrl) => setPendingImage(dataUrl)}
  onClose={() => setIsOpen(false)}
/>

// In your highlight container:
<ImageHighlight
  highlight={highlight}
  onChange={handlePositionChange}
  onEditStart={() => toggleEditInProgress(true)}
  onEditEnd={() => toggleEditInProgress(false)}
/>

Features:

  • Upload any image format
  • Draw signatures with mouse or touch
  • Drag to reposition
  • Resize while maintaining aspect ratio
  • Toolbar appears on hover

Full Documentation β†’

5. Freehand Drawing

Draw freehand annotations directly on PDFs.

import { DrawingHighlight } from "react-pdf-highlighter-plus";

<PdfHighlighter
  enableDrawingMode={drawingMode}
  onDrawingComplete={(dataUrl, position, strokes) => {
    addHighlight({ type: "drawing", position, content: { image: dataUrl } });
  }}
  drawingStrokeColor="#ff0000"
  drawingStrokeWidth={2}
>

// In your highlight container:
<DrawingHighlight
  highlight={highlight}
  onChange={handlePositionChange}
/>

Features:

  • Freehand drawing with mouse or touch
  • Customizable stroke color and width
  • Stored as PNG for PDF export compatibility
  • Drag to reposition

Full Documentation β†’


Shapes

Create rectangle, circle, and arrow annotations with editable stroke color and width.

<PdfHighlighter
  enableShapeMode={shapeMode} // "rectangle" | "circle" | "arrow" | null
  onShapeComplete={(position, shape) => {
    addHighlight({ type: "shape", position, content: { shape } });
  }}
  shapeStrokeColor="#000000"
  shapeStrokeWidth={2}
>
  <HighlightContainer />
</PdfHighlighter>

Shape geometry stays in the normal highlight layer while the style controls render in the higher config layer.

Full Documentation β†’


PDF Search

Use utilsRef to access document-wide search helpers backed by PDF.js PDFFindController.

const highlighterUtilsRef = useRef<PdfHighlighterUtils>();

<PdfHighlighter
  pdfDocument={pdfDocument}
  highlights={highlights}
  utilsRef={(utils) => (highlighterUtilsRef.current = utils)}
>
  <HighlightContainer />
</PdfHighlighter>

highlighterUtilsRef.current?.search("TypeScript", {
  highlightAll: true,
  caseSensitive: false,
});
highlighterUtilsRef.current?.findNext();
highlighterUtilsRef.current?.clearSearch();

Light/Dark Theme

Dark mode recolors each page at render time using a hue-preserving OKLab map β€” white paper maps to a dark background and black text to a light foreground, while colors keep their hue and embedded photos keep their pixels (unlike a CSS invert() filter). Highlights, the text selection, and the left panel all adapt.

// Enable dark mode (warm-gray default palette)
<PdfHighlighter
  pdfDocument={pdfDocument}
  theme={{ mode: "dark" }}
  highlights={highlights}
>
  <HighlightContainer />
</PdfHighlighter>

// Customize the dark palette
<PdfHighlighter
  pdfDocument={pdfDocument}
  theme={{
    mode: "dark",
    darkModeColors: {
      background: "#141210", // replaces white paper
      foreground: "#eae6e0", // replaces black text / line-art
    },
    containerBackgroundColor: "#3a3a3a",
    scrollbarThumbColor: "#6b6b6b",
    scrollbarTrackColor: "#2c2c2c",
  }}
  highlights={highlights}
>
  <HighlightContainer />
</PdfHighlighter>

Highlights:

  • Hue-preserving recolor (red stays red, blue links stay blue); photos untouched.
  • Highlights stay readable: translucent fill + a border, with no mix-blend wash-out.
  • Scroll and zoom are preserved when toggling the theme.
  • LeftPanel accepts mode="dark" so the outline/thumbnails panel matches.
  • Drawing/shape default ink becomes white in dark mode.

Deprecated: theme.darkModeInvertIntensity is ignored β€” dark mode no longer uses a CSS invert() filter. Use theme.darkModeColors instead.

Full Documentation β†’


Loading & Performance

PdfLoader loads documents progressively and caches them.

<PdfLoader
  document="https://api.example.com/files/report.pdf"
  // Fetch only the pages needed to render first (needs server HTTP range support)
  disableAutoFetch={true}
  // Auth / cross-origin
  httpHeaders={{ Authorization: `Bearer ${token}` }}
  withCredentials={false}
  // Reuse the same URL instantly on remount / re-open (default true)
  enableCache={true}
>
  {(pdfDocument) => <PdfHighlighter pdfDocument={pdfDocument} /* … */ />}
</PdfLoader>
Prop Default Description
disableAutoFetch true Fetch pages on demand instead of the whole file (first page shows fast on range-capable servers)
disableStream false Disable progressive streaming
rangeChunkSize pdf.js Size of each range request in bytes
httpHeaders – Extra request headers (e.g. an auth token)
withCredentials false Send cookies with the request
enableCache true Cache the loaded document by URL (also dedupes StrictMode double-mount)
beforeLoad spinner Render while loading; receives progress or null

Progressive loading requires the server to support HTTP range requests (Accept-Ranges: bytes) and expose Content-Range via CORS.


Navigation, Zoom & Smooth Scroll

<PdfHighlighter
  pdfDocument={pdfDocument}
  initialPage={12}                         // jump here on first load (deep-link)
  onPageChange={(page) => syncUrl(page)}    // current page changed
  onZoomChange={(scale) => setZoom(scale)}  // pinch / ctrl+wheel zoom changed
  highlights={highlights}
>
  <HighlightContainer />
</PdfHighlighter>
  • Pinch / ctrl(⌘)+wheel zoom is built in β€” smooth (GPU transform during the gesture, one crisp re-render on settle), anchored to the cursor.
  • scrollToHighlight(highlight) (from usePdfHighlighterContext) now scrolls smoothly and respects prefers-reduced-motion.
  • initialPage is applied once on load; onPageChange fires as the visible page changes β€” wire them to a ?page=N URL for deep links.

Locating Text (Citations)

getTextPosition finds a piece of text in the PDF and returns a precise ScaledPosition β€” turn an external quote (e.g. an AI citation) into a highlight you can render or scroll to. Matching ignores whitespace/line-wraps and falls back to fuzzy matching.

import { getTextPosition } from "react-pdf-highlighter-plus";

const match = await getTextPosition(pdfDocument, "the exact or near-exact quote");
if (match) {
  const citation = {
    id: "cite-1",
    type: "text",
    content: { text: match.matchedText },
    position: match.position,   // precise rects, page-independent
  };
  setHighlights((prev) => [citation, ...prev]);
  utils.scrollToHighlight(citation); // smooth scroll + flash
}
// match: { position, pageNumber, matchedText, confidence: "exact" | "fuzzy" }

Read Aloud (Text-to-Speech)

Build a read-aloud / "PDF to audio" experience: speak the document and highlight + auto-scroll to each sentence as it's read. The pieces are already here β€” extractSentences gives the ordered script (text + position) and scrollToHighlight follows along. The TTS engine is yours to choose (the browser speechSynthesis, or a cloud voice).

import { extractSentences } from "react-pdf-highlighter-plus";

// 1. Build the ordered script once.
const sentences = (
  await extractSentences(pdfDocument, { includePositions: true })
).filter((s) => s.position);

// 2. Speak each sentence; highlight + scroll as it starts.
function readFrom(index: number) {
  const s = sentences[index];
  if (!s) return;

  const reading = {
    id: "reading",
    type: "text",
    content: { text: s.text },
    position: s.position!,
  };
  setHighlights((prev) => [reading, ...prev.filter((h) => h.id !== "reading")]);
  utils.scrollToHighlight(reading); // smooth follow

  const utter = new SpeechSynthesisUtterance(s.text);
  utter.onend = () => readFrom(index + 1); // advance
  speechSynthesis.speak(utter);
}

readFrom(0);

Swap speechSynthesis for any engine β€” sentence-level sync needs no word timing. See the example app for a full transport (play/pause/seek/speed/voice).


PDF Export

Export your annotated PDF with all highlights embedded.

import { exportPdf } from "react-pdf-highlighter-plus";

const handleExport = async () => {
  const pdfBytes = await exportPdf(pdfUrl, highlights, {
    textHighlightColor: "rgba(255, 226, 143, 0.5)",
    areaHighlightColor: "rgba(255, 226, 143, 0.5)",
    onProgress: (current, total) => console.log(`${current}/${total} pages`),
  });

  // Download the file
  const blob = new Blob([pdfBytes], { type: "application/pdf" });
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = "annotated.pdf";
  a.click();
  URL.revokeObjectURL(url);
};

Supported highlight types:

  • Text highlights (colored rectangles)
  • Area highlights (colored rectangles)
  • Freetext notes (background + wrapped text)
  • Images & signatures (embedded PNG/JPG)
  • Freehand drawings (embedded PNG)
  • Shapes (rectangle, circle, arrow)

Full Documentation β†’


Component Architecture

The package renders React annotation components into PDF.js page overlay layers:

Layer Mount point Purpose
.PdfHighlighter__highlight-layer Inside PDF.js .textLayer Annotation geometry for text, area, image/signature, drawing, and shape
.PdfHighlighter__note-layer Direct child of PDF.js .page Freetext notes and compact note markers above PDF content
.PdfHighlighter__config-layer Direct child of PDF.js .page Toolbars, style panels, copy buttons, and controls above PDF content

Internal flow diagram:

Context Hooks

Hook Purpose
usePdfHighlighterContext() Viewer utilities: scrollToHighlight, setTip, getCurrentSelection
useHighlightContainerContext() Per-highlight utilities: highlight, viewportToScaled, screenshot

Coordinate Systems

The library uses two coordinate systems:

System Description Use Case
Viewport Pixel coordinates relative to current zoom Rendering on screen
Scaled Normalized (0-1) coordinates relative to page Storage & retrieval
// Converting between systems
const { viewportToScaled } = useHighlightContainerContext();

// Save position (viewport β†’ scaled)
const scaledPosition = viewportToScaled(boundingRect);

// Highlights are automatically converted to viewport when rendering

Customization

Custom Highlight Interface

interface MyHighlight extends Highlight {
  category: string;
  comment?: string;
  author?: string;
}

// Use the generic type
const { highlight } = useHighlightContainerContext<MyHighlight>();

Custom Styling

// Via props
<TextHighlight
  highlight={highlight}
  style={{ background: categoryColors[highlight.category] }}
/>

// Via CSS classes
.TextHighlight { }
.AreaHighlight { }
.FreetextHighlight { }
.ImageHighlight { }
.DrawingHighlight { }

Tips and Popups

import { MonitoredHighlightContainer } from "react-pdf-highlighter-plus";

<MonitoredHighlightContainer
  highlightTip={{
    position: highlight.position,
    content: <MyPopup highlight={highlight} />,
  }}
>
  <TextHighlight highlight={highlight} />
</MonitoredHighlightContainer>

Running Locally

git clone https://github.com/QuocVietHa08/react-pdf-highlighter-plus.git
cd react-pdf-highlighter-plus
npm install
npm run dev

API Reference

See the full API Reference for detailed documentation on all components and types.


Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

For bugs, please open an issue with clear reproduction steps.


License

MIT


Credits

Originally forked from react-pdf-highlighter with significant architectural changes including context-based APIs, zoom support, freetext/image/drawing highlights, and PDF export functionality.

Packages

 
 
 

Contributors

Languages

  • TypeScript 94.4%
  • CSS 5.1%
  • Other 0.5%