npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@epam/pdf-highlighter-kit

v0.0.4

Published

High-performance PDF viewer with intelligent highlighting and text selection capabilities

Readme

PDF Highlight Viewer

High-performance PDF viewer with intelligent highlighting and text selection capabilities for web applications.

Installation

npm install @epam/pdf-highlighter-kit pdfjs-dist

Peer dependency: pdfjs-dist (see Requirements).

Quick Start

import { PDFHighlightViewer } from '@epam/pdf-highlighter-kit';
import type { InputHighlightData } from '@epam/pdf-highlighter-kit';
import '@epam/pdf-highlighter-kit/styles/pdf-highlight-viewer.css';

// Create viewer instance
const viewer = new PDFHighlightViewer();

// Initialize with container element
const container = document.getElementById('pdf-container') as HTMLElement;

await viewer.init(container, {
  enableTextSelection: true,
  enableVirtualScrolling: true,
  bufferPages: 2,
  maxCachedPages: 10,
  interactionMode: 'hybrid',
});

// Load PDF document
await viewer.loadPDF('/path/to/document.pdf');

// Highlights
const highlights: InputHighlightData[] = [
  {
    id: 'term-001',
    bboxes: [
      // page is 1-based
      { page: 1, x1: 100, y1: 200, x2: 300, y2: 220 },
      { page: 3, x1: 80, y1: 140, x2: 260, y2: 160 },
    ],
    style: {
      backgroundColor: '#ffeb3b',
      opacity: 0.3,
      borderColor: '#d4c400',
      borderWidth: '1px',
    },
    tooltipText: 'Important Term',
    metadata: {
      frequency: 5,
      tags: ['important', 'glossary'],
    },
  },
];

viewer.loadHighlights(highlights);

// Navigate to a highlight occurrence
viewer.goToHighlight('term-001', 0);

Data Model

InputHighlightData

Each highlight carries its own style. No categories are required.

export interface BBox {
  x1: number;
  y1: number;
  x2: number;
  y2: number;
  page: number;
}

export interface HighlightStyle {
  backgroundColor: string;
  borderColor?: string;
  borderWidth?: string;
  opacity?: number;
  hoverOpacity?: number;
  pulseAnimation?: boolean;
}

export interface InputHighlightData {
  id: string;
  bboxes: BBox[];
  style?: HighlightStyle;
  tooltipText?: string;
  metadata?: Record<string, any>;
}

Configuration Options

ViewerConfig

interface ViewerConfig {
  // Enable text selection functionality
  enableTextSelection?: boolean;

  // Enable virtual scrolling for better performance
  enableVirtualScrolling?: boolean;

  // Number of pages to buffer above/below viewport
  bufferPages?: number;

  // Maximum number of pages to keep in cache
  maxCachedPages?: number;

  // Interaction mode: 'select' | 'highlight' | 'hybrid'
  interactionMode?: 'select' | 'highlight' | 'hybrid';

  // Custom styles configuration (viewer/selection CSS). Highlight styles are per-highlight.
  customStyles?: StyleConfig;

  // PDF.js worker source URL
  workerSrc?: string;

  // Highlight UI config (style is per highlight)
  highlightsConfig?: {
    enableMultilineHover?: boolean;
  };
}

API Reference

Main Methods

init(container: HTMLElement, config?: ViewerConfig): Promise<void>

Initialize the viewer with a container element and optional configuration.

loadPDF(source: string | ArrayBuffer): Promise<void>

Load a PDF document from URL or ArrayBuffer.

loadHighlights(highlights: InputHighlightData[]): void

Replace current highlights with the provided list.

addHighlight(highlight: InputHighlightData): void

Add a single highlight (incremental update).

removeHighlight(termId: string): void

Remove highlight by its id.

updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>): void

Update highlight style by id (patch merge).

goToHighlight(termId: string, bboxIndex?: number): void

Navigate to a specific highlight occurrence. bboxIndex defaults to 0.

nextHighlight(termId?: string): void / previousHighlight(termId?: string): void

Navigate across highlight occurrences:

  • without termId → across all highlights in document order
  • with termId → only within that highlight’s occurrences

goToPage(pageNumber: number): void

Navigate to a specific page (1-based).

zoomIn(): void / zoomOut(): void

Zoom in or out of the PDF.

setZoom(scale: number): void

Set a specific zoom level (e.g., 1.0 for 100%, 1.5 for 150%).

destroy(): void

Clean up and destroy the viewer instance.

Events

The viewer emits various events that you can listen to:

viewer.addEventListener('initialized', () => {
  console.log('Viewer initialized');
});

viewer.addEventListener('pdfLoaded', (e) => {
  console.log('PDF loaded. Total pages:', e.totalPages);
});

viewer.addEventListener('pageChanged', (e) => {
  console.log('Current page:', e.pageNumber);
});

viewer.addEventListener('zoomChanged', (e) => {
  console.log('Zoom changed:', e.scale);
});

viewer.addEventListener('renderComplete', (e) => {
  console.log('Page rendered:', e.pageNumber);
});

viewer.addEventListener('renderError', (e) => {
  console.error('Render error:', e.pageNumber, e.error);
});

viewer.addEventListener('highlightsLoaded', (e) => {
  console.log('Highlights loaded:', e.data?.length ?? 0);
});

viewer.addEventListener('highlightHover', (e) => {
  console.log('Highlight hover:', e.termId, 'page', e.pageNumber, 'bbox', e.bboxIndex);
});

viewer.addEventListener('highlightBlur', (e) => {
  console.log('Highlight blur:', e.termId);
});

viewer.addEventListener('highlightClick', (e) => {
  console.log('Highlight clicked:', e.termId, 'page', e.pageNumber, 'bbox', e.bboxIndex);
});

viewer.addEventListener('navigationComplete', (e) => {
  console.log('Navigation complete:', e.termId, e.pageNumber, e.occurrenceIndex);
});

viewer.addEventListener('selectionChanged', (e) => {
  console.log('Text selected:', e.text);
  console.log('Pages:', e.pageNumbers);
  console.log('Overlapping highlights:', e.highlights); // array of bbox refs
});

viewer.addEventListener('selectionHighlighted', (e) => {
  console.log('Selection highlighted:', e.termId);
});

viewer.addEventListener('error', (e) => {
  console.error('Viewer error:', e);
});

Event payload fields may include termId, pageNumber, bboxIndex, and bbox depending on event type.

Advanced Usage

Custom Styling (per highlight)

const highlight: InputHighlightData = {
  id: 'note-001',
  bboxes: [{ page: 2, x1: 120, y1: 330, x2: 420, y2: 355 }],
  style: {
    backgroundColor: '#ffd54f',
    opacity: 0.25,
    borderColor: '#ff8f00',
    borderWidth: '1px',
    hoverOpacity: 0.6,
  },
  tooltipText: 'My note',
};

viewer.addHighlight(highlight);

React Integration

import { useEffect, useRef } from 'react';
import { PDFHighlightViewer } from '@epam/pdf-highlighter-kit';
import type { InputHighlightData } from '@epam/pdf-highlighter-kit';
import '@epam/pdf-highlighter-kit/styles/pdf-highlight-viewer.css';

export function PDFViewer({
  pdfUrl,
  highlights,
}: {
  pdfUrl: string;
  highlights: InputHighlightData[];
}) {
  const containerRef = useRef<HTMLDivElement>(null);
  const viewerRef = useRef<PDFHighlightViewer | null>(null);

  useEffect(() => {
    if (!containerRef.current) return;

    const viewer = new PDFHighlightViewer();
    viewerRef.current = viewer;

    (async () => {
      await viewer.init(containerRef.current!, {
        enableTextSelection: true,
        enableVirtualScrolling: true,
      });

      await viewer.loadPDF(pdfUrl);
      viewer.loadHighlights(highlights);
    })();

    return () => {
      viewer.destroy();
      viewerRef.current = null;
    };
  }, [pdfUrl]);

  useEffect(() => {
    viewerRef.current?.loadHighlights(highlights);
  }, [highlights]);

  return <div ref={containerRef} style={{ width: '100%', height: '100vh' }} />;
}

Browser Support

  • Chrome/Edge (latest 2 versions)
  • Firefox (latest 2 versions)
  • Safari (latest 2 versions)

Requirements

  • pdfjs-dist: ^5.4.149 (peer dependency)

License

Apache-2.0

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

For issues and questions, please use the GitHub Issues page.