English | 简体中文
- ✍️ 丰富的注释系统
- 高亮、绘图、形状、文本注释
- 签名(手绘 / 输入 / 上传)
- 带编辑器支持的印章
- 直接编辑原生 PDF 注释
- 📄 基于 PDF.js 的高保真 PDF 渲染
- 🎨 基于 Radix UI Themes 的主题系统
- 🌍 国际化(中文简体、英文)
- 🧩 高度可自定义的 UI
- 工具栏 / 侧边栏 / 操作完全可覆盖
- 🏢 企业友好配置
defaultOptions支持深度部分 + 深度合并
- 💾 导出
- 将注释导出为 PDF
- 将注释导出为 Excel
- 🧠 为可扩展性而设计
- 清晰的上下文和扩展架构
- 矩形
- 圆形
- 自由手绘(短时间内绘制的会被分组)
- 自由高亮(带自动校正)
- 箭头
- 云线
- 自由文本
- 签名 (上传、手绘、输入、默认签名)
- 印章(上传自定义图片、自定义印章、 默认印章)
- 文本高亮
- 文本删除线
- 文本下划线
- 文本
- 矩形
- 圆形
- 绘制
- 自由文本
- 线条
- 多边形
- 折线
- 文本
- 高亮
- 下划线
- 删除线
npm install inklayer-react
or
yarn add inklayer-reactimport { PdfAnnotator } from 'inklayer-react'
import 'inklayer-react/style'
export default function App() {
return (
<PdfAnnotator
title="PDF Annotator"
url="https://example.com/sample.pdf"
user={{ id: 'u1', name: 'Alice' }}
onSave={(annotations) => {
console.log('Saved annotations:', annotations)
}}
/>
)
}import { PdfViewer } from 'inklayer-react'
import 'inklayer-react/style'
export default function App() {
return (
<PdfViewer
title="PDF Viewer"
url="https://example.com/sample.pdf"
layoutStyle={{ width: '100vw', height: '100vh' }}
/>
)
}| Name | Type | Default | Description |
|---|---|---|---|
appearance |
auto | dark | light |
auto |
Dark or Light theme. |
theme |
Radix Theme Color | violet |
Theme color of the viewer UI |
title |
React.ReactNode |
— | Page title content; accepts text or custom React nodes |
url |
string | URL |
— | PDF file URL; supports string URLs or URL objects |
data |
string | number[] | ArrayBuffer | Uint8Array | Uint16Array | Uint32Array |
— | PDF Data |
locale |
'zh-CN' | 'en-US' |
zh-CN |
Locale used for internationalization |
initialScale |
PdfScale |
auto |
Initial zoom level of the PDF viewer |
layoutStyle |
React.CSSProperties |
{ width: '100vw', height: '100vh' } |
Styles applied to the PDF viewer container |
isSidebarCollapsed |
boolean |
false |
Whether the sidebar is collapsed by default |
enableRange |
boolean | 'auto' |
auto |
Enables HTTP Range (streaming) loading for PDFs |
具有注释功能的高级 PDF 查看器
| Name | Type | Default | Description |
|---|---|---|---|
user |
User |
{ id: 'null', name: 'unknown' } |
Current user information used to identify the annotation author |
enableNativeAnnotations |
boolean |
false |
Native annotations embedded in the PDF file |
defaultShowAnnotationsSidebar |
boolean |
false |
Show Annotations Sidebar |
defaultOptions |
DeepPartial |
— | Default configuration for the annotator; |
initialAnnotations |
Annotation[] |
— | Existing annotations to be rendered during initialization |
actions |
React.ReactNode | React.ComponentType |
— | Custom actions area |
onSave |
(annotations: Annotation[]) => void |
— | Callback triggered when annotations are saved |
onLoad |
() => void |
— | Callback triggered when the PDF and annotator are fully loaded |
onAnnotationAdded |
(annotation: Annotation) => void |
— | Fired when a new annotation is created |
onAnnotationDeleted |
(id: string) => void |
— | Fired when an annotation is deleted |
onAnnotationSelected |
(annotation: Annotation | null, isClick: boolean) => void |
— | Fired when an annotation is selected or deselected |
onAnnotationUpdated |
(annotation: Annotation) => void |
— | Fired when an existing annotation is modified |
defaultOptions 不是完整的配置覆盖
- 它被定义为
DeepPartial<PdfAnnotatorOptions> - 它将与系统默认配置进行深度合并
这确保了:
- 您只需覆盖需要的内容
- 系统默认值保持稳定
- 适合长期企业使用
import qiantubifengshouxietiFont from './fonts/qiantubifengshouxieti.ttf';
<PdfAnnotator
url="sample.pdf"
defaultOptions={{
colors: ['#000', '#1677ff'],
signature: {
colors: ['#000000', '#ff0000', '#1677ff'],
type: 'Upload',
maxSize: 1024 * 1024 * 5,
accept: '.png,.jpg,.jpeg,.bmp',
defaultSignature: ['data:image/png;base64,...'],
defaultFont: [
{
label: '楷体',
value: 'STKaiti',
external: false
},
{
label: '千图笔锋手写体',
value: 'qiantubifengshouxieti',
external: true,
url: qiantubifengshouxietiFont
},
{
label: '平方长安体',
value: 'PingFangChangAnTi-2',
external: true,
url: 'http://server/PingFangChangAnTi-2.ttf'
}
]
},
stamp: {
maxSize: 1024 * 1024 * 5,
accept: '.png,.jpg,.jpeg,.bmp',
defaultStamp: ['data:image/png;base64,...'],
editor: {
defaultBackgroundColor: '#2f9e44',
defaultBorderColor: '#2b8a3e',
defaultBorderStyle: 'none',
defaultTextColor: '#fff',
defaultFont: [
{
label: '楷体',
value: 'STKaiti'
}
]
}
}
}}
/><PdfAnnotator
url={pdfUrl}
actions={({ save, exportToPdf, exportToExcel }) => (
<>
<button onClick={save}>Save</button>
<button onClick={() => exportToPdf('annotations')}>
Export PDF
</button>
<button onClick={() => exportToExcel('annotations')}>
Export Excel
</button>
</>
)}
/><PdfAnnotator
url={pdfUrl}
defaultOptions={{
signature: {
defaultSignature: ['data:image/png;base64,...'],
defaultFont: [
{
label: 'Custom Font',
value: 'MyFont',
external: true,
url: '/fonts/myfont.ttf'
}
]
},
stamp: {
defaultStamp: ['data:image/png;base64,...']
}
}}
/>具有工具栏、侧边栏、操作和可扩展 UI 插槽的轻量级 PDF 查看器。
| Name | Type | Default | Description |
|---|---|---|---|
actions |
React.ReactNode | (context: PdfViewerContextValue) => React.ReactNode |
— | Custom actions area in the toolbar |
sidebar |
SidebarPanel[] |
— | Custom sidebar component |
toolbar |
React.ReactNode | (context: PdfViewerContextValue) => React.ReactNode |
— | Custom toolbar component |
showTextLayer |
boolean |
true |
Whether to render the text layer |
showAnnotations |
boolean |
false |
Whether to render the pdf annotations layer |
defaultActiveSidebarKey |
string |
null | Default Active Sidebar Key |
onDocumentLoaded |
(pdfViewer: PDFViewer | null) => void |
— | Callback invoked when the PDF document is fully loaded and the viewer is initialized |
onEventBusReady |
(eventBus: EventBus | null) => void |
— | Callback invoked when the pdf.js EventBus is ready |
<PdfViewer
url={pdfUrl}
toolbar={(context) => (
<>
<button onClick={() => console.log(context.pdfViewer)}>
PDF Viewer
</button>
<button onClick={context.toggleSidebar}>
Toggle Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(false)}>
Open Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(true)}>
Close Sidebar
</button>
</>
)}
/><PdfViewer
url={pdfUrl}
sidebar={[{
key: 'sidebar-1',
title: 'Sidebar 1',
icon: <BsLayoutTextSidebar />,
render: (context) => (
<div style={{ display: 'flex', gap: 10, flexDirection: 'column' }}>
Sidebar 1
<button onClick={context.toggleSidebar}>
toggleSidebar
</button>
<button onClick={() => console.log(context.pdfViewer)}>
Get PDF Viewer
</button>
<button onClick={() => {
context.pdfViewer?.scrollPageIntoView({
pageNumber: 1
})
}}>
goto page1
</button>
<button onClick={() => {
context.pdfViewer?.scrollPageIntoView({
pageNumber: 10
})
}}>
goto page 10
</button>
</div>
)
}]}
/><PdfViewer
url={pdfUrl}
actions={(context) => (
<>
<button onClick={() => console.log(context.pdfViewer)}>
PDF Viewer
</button>
<button onClick={context.toggleSidebar}>
Toggle Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(false)}>
Open Sidebar
</button>
<button onClick={() => context.setSidebarCollapsed(true)}>
Close Sidebar
</button>
</>
)}
/>- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
MIT