安装
npm install @epam/pdf-highlighter-kit pdfjs-dist
同伴依赖 :
pdfjs-dist(见要求).
快速启动
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',
},
label: 'Important Term',
labelStyle: { fontSize: 11 },
tooltipText: 'Important Term',
metadata: {
frequency: 5,
tags: ['important', 'glossary'],
},
},
];
viewer.loadHighlights(highlights);
// Navigate to a highlight occurrence
viewer.goToHighlight('term-001', 0);
数据模型
InputHighlightData
每个亮点都带有自己的风格. 不需要类别.
标签 : 您可以添加可选 label 显示在亮点左侧,并冲向它。 默认情况下,标签使用亮点的颜色(从 style.borderColor 或者说 style.backgroundColor, (中文(简体) ), border: 1px solid,以及 padding: 2px 4px。用 labelStyle (例如:. fontSize, (中文(简体) ), color, (中文(简体) ), padding, (中文(简体) ), border) (中文(简体) ). 要隐藏标签边框, 设置 labelStyle: { border: 'none' } (默认边框除非被推翻,否则总是适用).
标签前的图标 : 可选设置 beforeIcon 改为内置 SVG 字符串(例如: 表格图标)在标签框内使图标出自文字的左边。 图标通过 currentColor启用 labelStyle.iconSize 以设置图标大小(例如). 14 或者说 '14px'和 labelStyle.iconColor 以设置图标颜色(例如). '#ff6b6b'; 如果 iconColor 未设置,图标使用标签文本颜色。 只能通过信任的 SVG 内容(例如从您的捆绑或 @tabler/icons); 在“与 Vite 反应”中, 您可以使用 import iconSvg from '@tabler/icons/icons/outline/alert-circle.svg?raw' 通过 iconSvg 作为 beforeIcon。 。 。 .
可缩放标签 : 设定 isLabelScalable: true 因此标签的大小(font, padding, 图标, 边框半径) 缩放; 标签与突出显示成正比 。 边框和轮廓厚度没有缩放(它们像亮点的轮廓一样固定)。 最佳用法为 labelStyle 数值 px 或数字.
export interface BBox {
x1: number;
y1: number;
x2: number;
y2: number;
page: number;
}
export interface BBoxDimensions {
width: number;
height: number;
}
export type BBoxOrigin = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
export type InteractionMode = 'select' | 'highlight' | 'hybrid';
export interface HighlightStyle {
backgroundColor: string;
borderColor?: string;
borderWidth?: string;
outline?: string;
opacity?: number;
hoverOpacity?: number;
pulseAnimation?: boolean;
}
export interface HighlightLabelStyle {
fontSize?: string | number;
opacity?: number;
color?: string;
backgroundColor?: string;
padding?: string;
borderRadius?: string; // e.g. '2px' or shorthand '2px 2px 0 0'
fontFamily?: string;
fontWeight?: string | number;
border?: string;
borderColor?: string;
borderWidth?: string;
outline?: string;
whiteSpace?: string;
iconSize?: string | number; // size for beforeIcon (e.g. 14 or '14px')
iconColor?: string;
offsetLeft?: number;
offsetTop?: number;
outlineRight?: string;
}
export interface InputHighlightData {
id: string;
bboxes: BBox[];
bboxOrigin?: BBoxOrigin;
bboxSourceDimensions?: BBoxDimensions;
style?: HighlightStyle;
label?: string;
beforeIcon?: string; // inline SVG string (trusted content only, e.g. Tabler icons)
isLabelScalable?: boolean; // when true, label size scales with zoom (borders stay fixed)
labelStyle?: HighlightLabelStyle;
tooltipText?: string;
metadata?: Record<string, any>;
}
若为 bboxSourceDimensions 提供每个 bbox 坐标根据实际页数重新计算:
scaledX = (x / bboxSourceDimensions.width) * actualPageWidth;
scaledY = (y / bboxSourceDimensions.height) * actualPageHeight;
优先权(最高至最低):
highlight.bboxOrigin编号 :highlight.bboxSourceDimensions- 全局查看器选项( e)
bboxOrigin, (中文(简体) ),bboxSourceDimensions(中文(简体) )
配置选项
查看器选项
export interface HighlightsConfig {
enableMultilineHover?: boolean;
defaultStyle?: HighlightStyle;
}
export interface ViewerOptions {
// 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?: InteractionMode;
// Performance mode (smaller frame budget)
performanceMode?: boolean;
// Enable accessibility helpers
accessibility?: boolean;
// Highlight UI config (style is per highlight)
highlightsConfig?: HighlightsConfig;
// Coordinate origin for incoming bbox values
// Default: 'bottom-right'
bboxOrigin?: BBoxOrigin;
// Page dimensions for which bbox coordinates were calculated
bboxSourceDimensions?: BBoxDimensions;
// Only show these document page numbers (1-based). If empty or omitted, all pages are shown.
selectedPages?: number[];
}
只显示所选页面 : 使用 selectedPages 以显示文档的子集(例如. [1, 3, 5] 或者说 [2, 4, 6, 8]) (中文(简体) ). 完整的PDF仍然被加载;只有列出的页面获得DOM容器并参与滚动和渲染. API中的页码(例如:. setPage,事件,要闻)仍然是物理文档的页码. 你可以通过 selectedPages 内 init 选项或每个负载通过 loadPDF(source, { selectedPages }); 后者覆盖了该负载的输入值. getTotalPages() 返回显示列表的长度,当 selectedPages 已经设定.
API 参考
主要方法
init(container: HTMLElement, config?: ViewerOptions): Promise<void>
用容器元素和可选配置初始化取景器.
loadPDF(source: string | ArrayBuffer | Blob, options?: LoadPDFOptions): Promise<void>
从 URL 或 ArrayBuffer 中装入 PDF 文档。 可选的第二个论点:
interface LoadPDFOptions {
/** Only show these document page numbers (1-based). If empty or omitted, all pages are shown. */
selectedPages?: number[];
}
示例 await viewer.loadPDF(url, { selectedPages: [1, 3, 5] }); 仅显示第1、第3和第5页。 何时 options.selectedPages 提供,它覆盖 ViewerOptions.selectedPages 为了这个负载.
loadHighlights(highlights: InputHighlightData[]): void
以所提供的列表替换当前亮点 .
addHighlight(highlight: InputHighlightData): void
增加一分亮度(递增更新).
removeHighlight(termId: string): void
删除其突出显示 id。 。 。 .
updateHighlightStyle(termId: string, stylePatch: Partial<HighlightStyle>, labelStylePatch?: Partial<HighlightLabelStyle>): void
由 id 更新突出显示样式( patch 合并) 。 可选通过 labelStylePatch 也可以更新标签样式.
goToHighlight(termId: string, occurrenceIndex?: number): void
导航到特定的突出显示事件 . occurrenceIndex 默认为 0。 。 。 .
nextHighlight(): void 编号 : previousHighlight(): void
按文档顺序浏览所有突出事件.
setPage(pageNumber: number): void
导航到一个特定的页面(1个基于).
zoomIn(): void 编号 : zoomOut(): void
在保留读取位置的同时缩放或退出 PDF 。 在浏览到一页后,突出显示或协调该目标在缩放时会停留在同一个取景点位置上,即使相邻的取景点占据了更多的取景点. 同一页和PDF-空间位置在连续的缩放变化中保持固定状态,包括当相邻页面在较小的尺度上变得更加明显时。 滚动或清晰的页面导航建立了一个新的锚. 页面检测使用实际的页面边框,包括对有混合页面大小的文档. 缩放承诺页面大小并一起滚动;上页图像在屏幕上保持缩放,直到新光栅准备好,而不改变加载指标的页面高度.
setZoom(value: ZoomMode | number): void
设定一个特定的缩放水平(例如:100%为1.0,150%为1.5). 读取位置保持不变 zoomIn() 财务报告和审定财务报表 zoomOut()。 。 。 .
getThumbnails(pageNumbers: number[], options?: ThumbnailOptions): Promise<Map<number, HTMLCanvasElement>>
给定的页码的 Render 页面缩略图( 最小) 。 回返a Map 的页码。 需要画布元素(例如用于绘图或自定义显示)时使用此功能. 选项 : maxWidth (目标宽度为px), scale (视图比例).
getThumbnailsDataUrl(pageNumbers: number[], options?: ThumbnailOptions): Promise<Map<number, string>>
与 getThumbnails 但返回数据 URL(例如用于 <img src="...">) (中文(简体) ). 选项包括 maxWidth, (中文(简体) ), scale, (中文(简体) ), format (单位:千美元)'image/jpeg' | 'image/webp' | 'image/png'),以及 quality (jpeg/webp为0–1). 缩略图被缓存; 重复调用同一页返回缓存结果 .
destroy(): void
清取并毁观相例.
活动
观众发布各种事件,你可以收听:
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.currentPage);
});
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.count);
});
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);
});
事件有效载荷场可能包括:
termId, (中文(简体) ),pageNumber, (中文(简体) ),bboxIndex,以及bbox根据事件类型.
高级使用
页面缩略图
// ThumbnailOptions: maxWidth?, scale?, format?, quality?
const pageNumbers = Array.from({ length: viewer.getTotalPages() }, (_, i) => i + 1);
// Option 1: get data URLs for <img src="...">
const dataUrls = await viewer.getThumbnailsDataUrl(pageNumbers, {
maxWidth: 120,
format: 'image/webp',
quality: 0.85,
});
// dataUrls is Map<number, string> — use dataUrls.get(pageNum) in img src
// Option 2: get canvases for custom rendering
const canvases = await viewer.getThumbnails(pageNumbers, { maxWidth: 120 });
// canvases is Map<number, HTMLCanvasElement>
对于一页,通过一个数组: getThumbnailsDataUrl([5], options)。缩略图会在取景器内被缓存;重新打开面板不会重新发送,除非缓存被清除(例如装入了新的PDF后).
自定义样式( 每高亮)
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,
},
label: 'My note',
labelStyle: { padding: '2px 6px', borderRadius: '4px' },
tooltipText: 'My note',
};
viewer.addHighlight(highlight);
他们
取景者的颜色跟随DIAL聊天调色板. 每种颜色都是 --phk-* 读取匹配的 DIAL 主题变量的 CSS 变量(--bg-layer-base, (中文(简体) ), --text-primary, (中文(简体) ), --stroke-accent(中文(简体) ). 因此,在DIAL app内部,查看器遵循的是活动主题,包括黑暗;在其他地方它使用DIAL光调色板.
要重塑查看器的样式, 请覆盖任意 --phk-* 变量 :
.pdf-highlight-viewer,
.pdf-context-menu,
.pdf-tooltip {
--phk-bg: #ffffff;
--phk-accent: #0f766e;
--phk-yellow: #facc15; /* protein highlights and search results */
}
名单排在首位 styles/pdf-highlight-viewer.css。 。 。 .
有图标的标签( 如 Taller 图标)
通过内置 SVG 字符串为 beforeIcon 在标签框内显示图标。 只使用可信任的内容 。 使用 Vite 可以导入 SVG 作为原始字符串 :
// Inline SVG string (e.g. from Tabler: import icon from '@tabler/icons/icons/outline/alert-circle.svg?raw')
const alertIconSvg =
'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 8v4"/><path d="M12 16h.01"/><circle cx="12" cy="12" r="10"/></svg>';
const highlight: InputHighlightData = {
id: 'alert-001',
bboxes: [{ page: 1, x1: 100, y1: 200, x2: 300, y2: 220 }],
style: { backgroundColor: '#ffeb3b', opacity: 0.3, borderColor: '#d4c400' },
label: 'Important',
beforeIcon: alertIconSvg,
labelStyle: { fontSize: 11, iconSize: 14 },
tooltipText: 'Important',
};
viewer.addHighlight(highlight);
反应集成
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' }} />;
}
浏览器支持
- Chrome/Edge(最新2个版本)
- Firefox(最新2个版本)
- Safari(最新2个版本)
所需资源
pdfjs-dist: ^ 5.4.149 ^ 6.0.0 (同龄人依赖关系)
许可证
阿帕奇 - 2.0
捐款
欢迎捐款! 请随意提交拉力请求.
支助
关于议题和问题,请使用GitHub问题页面.