pdfjs-reader-core 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +678 -423
- package/dist/index.cjs +3442 -582
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +745 -2
- package/dist/index.d.ts +745 -2
- package/dist/index.js +3388 -561
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
# pdfjs-reader-core
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **PDF Rendering** - High-quality canvas-based rendering using PDF.js
|
|
8
|
-
- **Text Selection & Highlighting** - Select text and highlight with multiple colors
|
|
9
|
-
- **Annotations** - Add sticky notes, freehand drawings, and shapes (rectangles, circles, arrows, lines)
|
|
10
|
-
- **Search** - Full-text search across all pages with match highlighting
|
|
11
|
-
- **Multiple View Modes** - Single page, continuous scroll, and dual page views
|
|
12
|
-
- **Thumbnails & Outline** - Page thumbnails and document outline navigation
|
|
13
|
-
- **Theming** - Light, dark, and sepia themes
|
|
14
|
-
- **Mobile Support** - Touch gestures, pinch-to-zoom, responsive UI
|
|
15
|
-
- **Programmatic API** - Full control via React hooks
|
|
3
|
+
A React library for rendering PDFs with built-in search, highlighting, and annotation capabilities.
|
|
16
4
|
|
|
17
5
|
## Installation
|
|
18
6
|
|
|
@@ -24,7 +12,13 @@ yarn add pdfjs-reader-core
|
|
|
24
12
|
pnpm add pdfjs-reader-core
|
|
25
13
|
```
|
|
26
14
|
|
|
27
|
-
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 1. Rendering PDFs
|
|
18
|
+
|
|
19
|
+
### Quick Start - Full-Featured Viewer
|
|
20
|
+
|
|
21
|
+
The easiest way to render a PDF with all features enabled:
|
|
28
22
|
|
|
29
23
|
```tsx
|
|
30
24
|
import { PDFViewerClient } from 'pdfjs-reader-core';
|
|
@@ -37,344 +31,675 @@ function App() {
|
|
|
37
31
|
src="/document.pdf"
|
|
38
32
|
showToolbar
|
|
39
33
|
showSidebar
|
|
34
|
+
onDocumentLoad={({ numPages }) => console.log(`Loaded ${numPages} pages`)}
|
|
35
|
+
onError={(error) => console.error('Failed to load:', error)}
|
|
40
36
|
/>
|
|
41
37
|
</div>
|
|
42
38
|
);
|
|
43
39
|
}
|
|
44
40
|
```
|
|
45
41
|
|
|
46
|
-
|
|
42
|
+
### Custom Viewer with Hooks
|
|
47
43
|
|
|
48
|
-
|
|
44
|
+
For more control, use the provider and hooks:
|
|
49
45
|
|
|
50
46
|
```tsx
|
|
51
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
PDFViewerProvider,
|
|
49
|
+
usePDFViewer,
|
|
50
|
+
ContinuousScrollContainer,
|
|
51
|
+
Toolbar,
|
|
52
|
+
Sidebar,
|
|
53
|
+
} from 'pdfjs-reader-core';
|
|
52
54
|
import 'pdfjs-reader-core/styles.css';
|
|
53
55
|
|
|
54
|
-
function
|
|
56
|
+
function App() {
|
|
55
57
|
return (
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
showSidebar={true}
|
|
60
|
-
theme="light"
|
|
61
|
-
onDocumentLoad={({ numPages }) => console.log(`Loaded ${numPages} pages`)}
|
|
62
|
-
onError={(error) => console.error('Failed to load:', error)}
|
|
63
|
-
/>
|
|
58
|
+
<PDFViewerProvider>
|
|
59
|
+
<MyPDFViewer />
|
|
60
|
+
</PDFViewerProvider>
|
|
64
61
|
);
|
|
65
62
|
}
|
|
66
|
-
```
|
|
67
63
|
|
|
68
|
-
|
|
64
|
+
function MyPDFViewer() {
|
|
65
|
+
const { loadDocument, isLoading, error, numPages } = usePDFViewer();
|
|
69
66
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
loadDocument({ src: '/document.pdf' });
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
if (isLoading) return <div>Loading...</div>;
|
|
72
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
73
73
|
|
|
74
|
-
function AnnotationViewer() {
|
|
75
74
|
return (
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
/>
|
|
75
|
+
<div style={{ display: 'flex', height: '100vh' }}>
|
|
76
|
+
<Sidebar />
|
|
77
|
+
<div style={{ flex: 1, display: 'flex', flexDirection: 'column' }}>
|
|
78
|
+
<Toolbar />
|
|
79
|
+
<ContinuousScrollContainer />
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
84
82
|
);
|
|
85
83
|
}
|
|
86
84
|
```
|
|
87
85
|
|
|
88
|
-
###
|
|
86
|
+
### Load PDF from Different Sources
|
|
89
87
|
|
|
90
|
-
|
|
88
|
+
```tsx
|
|
89
|
+
const { loadDocument } = usePDFViewer();
|
|
90
|
+
|
|
91
|
+
// From URL
|
|
92
|
+
await loadDocument({ src: 'https://example.com/document.pdf' });
|
|
93
|
+
|
|
94
|
+
// From file input
|
|
95
|
+
const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
96
|
+
const file = e.target.files?.[0];
|
|
97
|
+
if (file) {
|
|
98
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
99
|
+
await loadDocument({ src: arrayBuffer });
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// From base64
|
|
104
|
+
const base64 = 'JVBERi0xLjQK...';
|
|
105
|
+
const binaryString = atob(base64);
|
|
106
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
107
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
108
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
109
|
+
}
|
|
110
|
+
await loadDocument({ src: bytes });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Navigation API
|
|
91
114
|
|
|
92
115
|
```tsx
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
116
|
+
const {
|
|
117
|
+
currentPage, // Current page number (1-indexed)
|
|
118
|
+
numPages, // Total pages
|
|
119
|
+
scale, // Current zoom level (1 = 100%)
|
|
120
|
+
goToPage, // Navigate to specific page
|
|
121
|
+
nextPage, // Go to next page
|
|
122
|
+
previousPage, // Go to previous page
|
|
123
|
+
setScale, // Set zoom level
|
|
124
|
+
zoomIn, // Zoom in by preset amount
|
|
125
|
+
zoomOut, // Zoom out by preset amount
|
|
126
|
+
fitToWidth, // Fit page to container width
|
|
127
|
+
fitToPage, // Fit entire page in view
|
|
128
|
+
rotateClockwise, // Rotate 90° clockwise
|
|
129
|
+
} = usePDFViewer();
|
|
101
130
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
131
|
+
// Examples
|
|
132
|
+
goToPage(5); // Go to page 5
|
|
133
|
+
setScale(1.5); // Set zoom to 150%
|
|
134
|
+
zoomIn(); // Zoom in
|
|
135
|
+
fitToWidth(); // Fit to width
|
|
136
|
+
rotateClockwise(); // Rotate
|
|
137
|
+
```
|
|
105
138
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 2. Search
|
|
142
|
+
|
|
143
|
+
Search text across all pages and navigate through results.
|
|
144
|
+
|
|
145
|
+
### Basic Search
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
const {
|
|
149
|
+
search, // (query: string) => Promise<void>
|
|
150
|
+
searchResults, // Array of search results
|
|
151
|
+
currentSearchResult, // Index of current result
|
|
152
|
+
nextSearchResult, // Go to next result
|
|
153
|
+
previousSearchResult, // Go to previous result
|
|
154
|
+
clearSearch, // Clear search
|
|
155
|
+
goToPage, // Navigate to page
|
|
156
|
+
} = usePDFViewer();
|
|
157
|
+
|
|
158
|
+
// Perform search
|
|
159
|
+
await search('important term');
|
|
160
|
+
|
|
161
|
+
// Navigate results
|
|
162
|
+
console.log(`Found ${searchResults.length} matches`);
|
|
163
|
+
nextSearchResult(); // Go to next match
|
|
164
|
+
previousSearchResult(); // Go to previous match
|
|
165
|
+
|
|
166
|
+
// Clear when done
|
|
167
|
+
clearSearch();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Search Result Structure
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface SearchResult {
|
|
174
|
+
pageNumber: number; // Page where match was found
|
|
175
|
+
text: string; // Matched text
|
|
176
|
+
index: number; // Index in results array
|
|
177
|
+
rects?: { // Bounding rectangles for highlighting
|
|
178
|
+
x: number;
|
|
179
|
+
y: number;
|
|
180
|
+
width: number;
|
|
181
|
+
height: number;
|
|
182
|
+
}[];
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Complete Search UI Example
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
import { useState } from 'react';
|
|
190
|
+
import { usePDFViewer } from 'pdfjs-reader-core';
|
|
191
|
+
|
|
192
|
+
function SearchBar() {
|
|
193
|
+
const [query, setQuery] = useState('');
|
|
194
|
+
const {
|
|
195
|
+
search,
|
|
196
|
+
searchResults,
|
|
197
|
+
currentSearchResult,
|
|
198
|
+
nextSearchResult,
|
|
199
|
+
previousSearchResult,
|
|
200
|
+
clearSearch,
|
|
201
|
+
} = usePDFViewer();
|
|
202
|
+
|
|
203
|
+
const handleSearch = async (text: string) => {
|
|
204
|
+
setQuery(text);
|
|
205
|
+
if (text.length >= 2) {
|
|
206
|
+
await search(text);
|
|
207
|
+
} else {
|
|
208
|
+
clearSearch();
|
|
209
|
+
}
|
|
117
210
|
};
|
|
118
211
|
|
|
119
212
|
return (
|
|
120
213
|
<div>
|
|
121
|
-
<
|
|
122
|
-
|
|
214
|
+
<input
|
|
215
|
+
type="text"
|
|
216
|
+
value={query}
|
|
217
|
+
onChange={(e) => handleSearch(e.target.value)}
|
|
218
|
+
placeholder="Search..."
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
{searchResults.length > 0 && (
|
|
222
|
+
<div>
|
|
223
|
+
<span>
|
|
224
|
+
{currentSearchResult + 1} of {searchResults.length}
|
|
225
|
+
</span>
|
|
226
|
+
<button onClick={previousSearchResult}>←</button>
|
|
227
|
+
<button onClick={nextSearchResult}>→</button>
|
|
228
|
+
<button onClick={clearSearch}>Clear</button>
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
123
231
|
</div>
|
|
124
232
|
);
|
|
125
233
|
}
|
|
234
|
+
```
|
|
126
235
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 3. Highlighting
|
|
239
|
+
|
|
240
|
+
Create persistent highlights on PDF text. Highlights are rendered as colored overlays.
|
|
241
|
+
|
|
242
|
+
### Add Highlight Programmatically
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
const { addHighlight, highlights, removeHighlight } = usePDFViewer();
|
|
246
|
+
|
|
247
|
+
// Add a highlight with coordinates
|
|
248
|
+
const highlight = addHighlight({
|
|
249
|
+
pageNumber: 1,
|
|
250
|
+
text: 'The highlighted text',
|
|
251
|
+
color: 'yellow', // 'yellow' | 'green' | 'blue' | 'pink' | 'orange'
|
|
252
|
+
rects: [
|
|
253
|
+
{ x: 72, y: 100, width: 200, height: 14 },
|
|
254
|
+
{ x: 72, y: 116, width: 150, height: 14 }, // Multi-line support
|
|
255
|
+
],
|
|
256
|
+
comment: 'Optional note', // Optional
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
console.log(highlight.id); // Unique ID for the highlight
|
|
260
|
+
|
|
261
|
+
// List all highlights
|
|
262
|
+
highlights.forEach(h => {
|
|
263
|
+
console.log(`Page ${h.pageNumber}: "${h.text}" (${h.color})`);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Remove a highlight
|
|
267
|
+
removeHighlight(highlight.id);
|
|
137
268
|
```
|
|
138
269
|
|
|
139
|
-
###
|
|
270
|
+
### Highlight from Search Results
|
|
140
271
|
|
|
141
|
-
|
|
272
|
+
Convert search results into permanent highlights:
|
|
142
273
|
|
|
143
274
|
```tsx
|
|
144
|
-
|
|
275
|
+
const { search, searchResults, addHighlight } = usePDFViewer();
|
|
145
276
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const { currentPage } = usePDFViewer();
|
|
277
|
+
// Search for a term
|
|
278
|
+
await search('important');
|
|
149
279
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
color: '#ef4444',
|
|
159
|
-
strokeWidth: 2,
|
|
280
|
+
// Highlight all matches
|
|
281
|
+
searchResults.forEach((result) => {
|
|
282
|
+
if (result.rects && result.rects.length > 0) {
|
|
283
|
+
addHighlight({
|
|
284
|
+
pageNumber: result.pageNumber,
|
|
285
|
+
text: result.text,
|
|
286
|
+
rects: result.rects,
|
|
287
|
+
color: 'yellow',
|
|
160
288
|
});
|
|
161
|
-
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
```
|
|
162
292
|
|
|
163
|
-
|
|
164
|
-
createShape({
|
|
165
|
-
pageNumber: currentPage,
|
|
166
|
-
shapeType: 'circle',
|
|
167
|
-
x: 300,
|
|
168
|
-
y: 200,
|
|
169
|
-
width: 80,
|
|
170
|
-
height: 80,
|
|
171
|
-
color: '#3b82f6',
|
|
172
|
-
strokeWidth: 2,
|
|
173
|
-
});
|
|
174
|
-
};
|
|
293
|
+
### Using the useHighlights Hook
|
|
175
294
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
295
|
+
For more control over highlights:
|
|
296
|
+
|
|
297
|
+
```tsx
|
|
298
|
+
import { useHighlights } from 'pdfjs-reader-core';
|
|
299
|
+
|
|
300
|
+
function HighlightManager() {
|
|
301
|
+
const {
|
|
302
|
+
allHighlights, // All highlights
|
|
303
|
+
highlightsForPage, // (pageNum) => highlights on that page
|
|
304
|
+
addHighlight, // Add new highlight
|
|
305
|
+
updateHighlight, // Update existing
|
|
306
|
+
deleteHighlight, // Delete by ID
|
|
307
|
+
selectedHighlight, // Currently selected highlight
|
|
308
|
+
selectHighlight, // Select a highlight
|
|
309
|
+
createHighlightFromSelection, // Create from text selection
|
|
310
|
+
} = useHighlights({
|
|
311
|
+
onHighlightCreate: (h) => console.log('Created:', h),
|
|
312
|
+
onHighlightUpdate: (h) => console.log('Updated:', h),
|
|
313
|
+
onHighlightDelete: (id) => console.log('Deleted:', id),
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Get highlights for page 1
|
|
317
|
+
const page1Highlights = highlightsForPage(1);
|
|
318
|
+
|
|
319
|
+
// Update a highlight's color
|
|
320
|
+
updateHighlight('highlight-id', { color: 'green' });
|
|
321
|
+
|
|
322
|
+
// Add a comment to highlight
|
|
323
|
+
updateHighlight('highlight-id', { comment: 'This is important!' });
|
|
188
324
|
|
|
189
325
|
return (
|
|
190
326
|
<div>
|
|
191
|
-
<
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
327
|
+
<h3>Highlights ({allHighlights.length})</h3>
|
|
328
|
+
{allHighlights.map(h => (
|
|
329
|
+
<div key={h.id} onClick={() => selectHighlight(h.id)}>
|
|
330
|
+
<span style={{ background: h.color }}>{h.text}</span>
|
|
331
|
+
<button onClick={() => deleteHighlight(h.id)}>Delete</button>
|
|
332
|
+
</div>
|
|
333
|
+
))}
|
|
195
334
|
</div>
|
|
196
335
|
);
|
|
197
336
|
}
|
|
198
337
|
```
|
|
199
338
|
|
|
200
|
-
###
|
|
339
|
+
### Highlight Type Definition
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
interface Highlight {
|
|
343
|
+
id: string;
|
|
344
|
+
pageNumber: number;
|
|
345
|
+
rects: { x: number; y: number; width: number; height: number }[];
|
|
346
|
+
text: string;
|
|
347
|
+
color: 'yellow' | 'green' | 'blue' | 'pink' | 'orange';
|
|
348
|
+
comment?: string;
|
|
349
|
+
source?: 'user' | 'agent'; // Who created it
|
|
350
|
+
createdAt: Date;
|
|
351
|
+
updatedAt: Date;
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Persist Highlights
|
|
201
356
|
|
|
202
|
-
|
|
357
|
+
Save and restore highlights:
|
|
203
358
|
|
|
204
359
|
```tsx
|
|
205
|
-
import {
|
|
360
|
+
import {
|
|
361
|
+
saveHighlights,
|
|
362
|
+
loadHighlights,
|
|
363
|
+
exportHighlightsAsJSON,
|
|
364
|
+
importHighlightsFromJSON,
|
|
365
|
+
} from 'pdfjs-reader-core';
|
|
206
366
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const { addHighlight } = useHighlights();
|
|
210
|
-
const [query, setQuery] = useState('');
|
|
367
|
+
// Save to localStorage
|
|
368
|
+
saveHighlights('doc-123', highlights);
|
|
211
369
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
};
|
|
370
|
+
// Load from localStorage
|
|
371
|
+
const saved = loadHighlights('doc-123');
|
|
215
372
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
373
|
+
// Export as JSON file
|
|
374
|
+
exportHighlightsAsJSON(highlights, 'my-highlights.json');
|
|
375
|
+
|
|
376
|
+
// Import from JSON
|
|
377
|
+
const imported = await importHighlightsFromJSON(jsonFile);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## 4. Annotations
|
|
383
|
+
|
|
384
|
+
Add notes, drawings, and shapes to PDFs.
|
|
385
|
+
|
|
386
|
+
### Add Sticky Notes
|
|
387
|
+
|
|
388
|
+
```tsx
|
|
389
|
+
import { useAnnotationStore } from 'pdfjs-reader-core';
|
|
390
|
+
|
|
391
|
+
function NoteManager() {
|
|
392
|
+
const addNote = useAnnotationStore((s) => s.addNote);
|
|
393
|
+
const annotations = useAnnotationStore((s) => s.annotations);
|
|
394
|
+
const deleteAnnotation = useAnnotationStore((s) => s.deleteAnnotation);
|
|
395
|
+
|
|
396
|
+
// Add a note at specific position
|
|
397
|
+
const createNote = () => {
|
|
398
|
+
addNote({
|
|
399
|
+
pageNumber: 1,
|
|
400
|
+
x: 100, // X position in PDF points
|
|
401
|
+
y: 200, // Y position in PDF points
|
|
402
|
+
content: 'This is my note',
|
|
403
|
+
color: '#ffeb3b', // Note color
|
|
404
|
+
});
|
|
231
405
|
};
|
|
232
406
|
|
|
407
|
+
// List all notes
|
|
408
|
+
const notes = annotations.filter(a => a.type === 'note');
|
|
409
|
+
|
|
233
410
|
return (
|
|
234
411
|
<div>
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
Highlight All ({searchResults.length} matches)
|
|
243
|
-
</button>
|
|
412
|
+
<button onClick={createNote}>Add Note</button>
|
|
413
|
+
{notes.map(note => (
|
|
414
|
+
<div key={note.id}>
|
|
415
|
+
Page {note.pageNumber}: {note.content}
|
|
416
|
+
<button onClick={() => deleteAnnotation(note.id)}>Delete</button>
|
|
417
|
+
</div>
|
|
418
|
+
))}
|
|
244
419
|
</div>
|
|
245
420
|
);
|
|
246
421
|
}
|
|
247
422
|
```
|
|
248
423
|
|
|
249
|
-
###
|
|
424
|
+
### Add Shapes
|
|
250
425
|
|
|
251
426
|
```tsx
|
|
252
|
-
|
|
427
|
+
const addShape = useAnnotationStore((s) => s.addShape);
|
|
253
428
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
fitToWidth,
|
|
266
|
-
fitToPage,
|
|
267
|
-
rotateClockwise,
|
|
268
|
-
} = usePDFViewer();
|
|
429
|
+
// Rectangle
|
|
430
|
+
addShape({
|
|
431
|
+
pageNumber: 1,
|
|
432
|
+
shapeType: 'rect',
|
|
433
|
+
x: 100,
|
|
434
|
+
y: 200,
|
|
435
|
+
width: 150,
|
|
436
|
+
height: 80,
|
|
437
|
+
color: '#ef4444',
|
|
438
|
+
strokeWidth: 2,
|
|
439
|
+
});
|
|
269
440
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
441
|
+
// Circle
|
|
442
|
+
addShape({
|
|
443
|
+
pageNumber: 1,
|
|
444
|
+
shapeType: 'circle',
|
|
445
|
+
x: 300,
|
|
446
|
+
y: 200,
|
|
447
|
+
width: 100,
|
|
448
|
+
height: 100,
|
|
449
|
+
color: '#22c55e',
|
|
450
|
+
strokeWidth: 2,
|
|
451
|
+
});
|
|
276
452
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
453
|
+
// Arrow
|
|
454
|
+
addShape({
|
|
455
|
+
pageNumber: 1,
|
|
456
|
+
shapeType: 'arrow',
|
|
457
|
+
x: 100,
|
|
458
|
+
y: 350,
|
|
459
|
+
width: 120,
|
|
460
|
+
height: 40,
|
|
461
|
+
color: '#3b82f6',
|
|
462
|
+
strokeWidth: 3,
|
|
463
|
+
});
|
|
285
464
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
465
|
+
// Line
|
|
466
|
+
addShape({
|
|
467
|
+
pageNumber: 1,
|
|
468
|
+
shapeType: 'line',
|
|
469
|
+
x: 100,
|
|
470
|
+
y: 450,
|
|
471
|
+
width: 200,
|
|
472
|
+
height: 0,
|
|
473
|
+
color: '#000000',
|
|
474
|
+
strokeWidth: 2,
|
|
475
|
+
});
|
|
476
|
+
```
|
|
292
477
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
478
|
+
### Freehand Drawing
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
const startDrawing = useAnnotationStore((s) => s.startDrawing);
|
|
482
|
+
const addDrawingPoint = useAnnotationStore((s) => s.addDrawingPoint);
|
|
483
|
+
const finishDrawing = useAnnotationStore((s) => s.finishDrawing);
|
|
484
|
+
const setDrawingColor = useAnnotationStore((s) => s.setDrawingColor);
|
|
485
|
+
const setDrawingStrokeWidth = useAnnotationStore((s) => s.setDrawingStrokeWidth);
|
|
486
|
+
|
|
487
|
+
// Configure drawing
|
|
488
|
+
setDrawingColor('#ff0000');
|
|
489
|
+
setDrawingStrokeWidth(3);
|
|
490
|
+
|
|
491
|
+
// Start drawing on page 1 at position (100, 200)
|
|
492
|
+
startDrawing(1, { x: 100, y: 200 });
|
|
493
|
+
|
|
494
|
+
// Add points as user draws
|
|
495
|
+
addDrawingPoint({ x: 110, y: 210 });
|
|
496
|
+
addDrawingPoint({ x: 120, y: 205 });
|
|
497
|
+
addDrawingPoint({ x: 130, y: 215 });
|
|
498
|
+
|
|
499
|
+
// Finish drawing (saves the annotation)
|
|
500
|
+
finishDrawing();
|
|
298
501
|
```
|
|
299
502
|
|
|
300
|
-
###
|
|
503
|
+
### Enable Drawing Mode UI
|
|
301
504
|
|
|
302
505
|
```tsx
|
|
303
|
-
|
|
506
|
+
const setActiveAnnotationTool = useAnnotationStore((s) => s.setActiveAnnotationTool);
|
|
507
|
+
const activeAnnotationTool = useAnnotationStore((s) => s.activeAnnotationTool);
|
|
304
508
|
|
|
305
|
-
|
|
306
|
-
|
|
509
|
+
// Enable drawing mode
|
|
510
|
+
setActiveAnnotationTool('draw');
|
|
307
511
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
512
|
+
// Enable note mode (click to add notes)
|
|
513
|
+
setActiveAnnotationTool('note');
|
|
514
|
+
|
|
515
|
+
// Enable shape mode
|
|
516
|
+
setActiveAnnotationTool('shape');
|
|
517
|
+
|
|
518
|
+
// Disable annotation mode
|
|
519
|
+
setActiveAnnotationTool(null);
|
|
520
|
+
|
|
521
|
+
// Check current mode
|
|
522
|
+
if (activeAnnotationTool === 'draw') {
|
|
523
|
+
console.log('Drawing mode is active');
|
|
315
524
|
}
|
|
316
525
|
```
|
|
317
526
|
|
|
318
|
-
###
|
|
527
|
+
### Annotation Type Definition
|
|
319
528
|
|
|
320
|
-
```
|
|
321
|
-
|
|
529
|
+
```typescript
|
|
530
|
+
interface Annotation {
|
|
531
|
+
id: string;
|
|
532
|
+
pageNumber: number;
|
|
533
|
+
type: 'note' | 'drawing' | 'shape';
|
|
322
534
|
|
|
323
|
-
|
|
324
|
-
|
|
535
|
+
// For notes
|
|
536
|
+
content?: string;
|
|
537
|
+
x?: number;
|
|
538
|
+
y?: number;
|
|
325
539
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
540
|
+
// For shapes
|
|
541
|
+
shapeType?: 'rect' | 'circle' | 'arrow' | 'line';
|
|
542
|
+
width?: number;
|
|
543
|
+
height?: number;
|
|
544
|
+
|
|
545
|
+
// For drawings
|
|
546
|
+
points?: { x: number; y: number }[];
|
|
547
|
+
|
|
548
|
+
// Common
|
|
549
|
+
color: string;
|
|
550
|
+
strokeWidth?: number;
|
|
551
|
+
createdAt: Date;
|
|
552
|
+
updatedAt: Date;
|
|
333
553
|
}
|
|
334
554
|
```
|
|
335
555
|
|
|
336
|
-
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## 5. Complete Example
|
|
559
|
+
|
|
560
|
+
Here's a full example combining rendering, search, highlighting, and annotations:
|
|
337
561
|
|
|
338
562
|
```tsx
|
|
339
|
-
import {
|
|
563
|
+
import { useState, useEffect } from 'react';
|
|
564
|
+
import {
|
|
565
|
+
PDFViewerProvider,
|
|
566
|
+
usePDFViewer,
|
|
567
|
+
useHighlights,
|
|
568
|
+
useAnnotationStore,
|
|
569
|
+
ContinuousScrollContainer,
|
|
570
|
+
} from 'pdfjs-reader-core';
|
|
571
|
+
import 'pdfjs-reader-core/styles.css';
|
|
340
572
|
|
|
341
|
-
function
|
|
342
|
-
|
|
343
|
-
|
|
573
|
+
function App() {
|
|
574
|
+
return (
|
|
575
|
+
<PDFViewerProvider>
|
|
576
|
+
<div style={{ display: 'flex', height: '100vh' }}>
|
|
577
|
+
<ControlPanel />
|
|
578
|
+
<div style={{ flex: 1 }}>
|
|
579
|
+
<ContinuousScrollContainer />
|
|
580
|
+
</div>
|
|
581
|
+
</div>
|
|
582
|
+
</PDFViewerProvider>
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function ControlPanel() {
|
|
587
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
588
|
+
|
|
589
|
+
// PDF viewer controls
|
|
590
|
+
const {
|
|
591
|
+
loadDocument,
|
|
592
|
+
currentPage,
|
|
593
|
+
numPages,
|
|
594
|
+
goToPage,
|
|
595
|
+
search,
|
|
596
|
+
searchResults,
|
|
597
|
+
clearSearch,
|
|
598
|
+
} = usePDFViewer();
|
|
344
599
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
600
|
+
// Highlight controls
|
|
601
|
+
const { allHighlights, addHighlight, deleteHighlight } = useHighlights();
|
|
602
|
+
|
|
603
|
+
// Annotation controls
|
|
604
|
+
const addNote = useAnnotationStore((s) => s.addNote);
|
|
605
|
+
const annotations = useAnnotationStore((s) => s.annotations);
|
|
606
|
+
|
|
607
|
+
// Load PDF on mount
|
|
608
|
+
useEffect(() => {
|
|
609
|
+
loadDocument({ src: '/sample.pdf' });
|
|
610
|
+
}, []);
|
|
611
|
+
|
|
612
|
+
// Search handler
|
|
613
|
+
const handleSearch = async () => {
|
|
614
|
+
if (searchQuery.length >= 2) {
|
|
615
|
+
await search(searchQuery);
|
|
616
|
+
}
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// Highlight all search results
|
|
620
|
+
const highlightSearchResults = () => {
|
|
621
|
+
searchResults.forEach((result) => {
|
|
622
|
+
if (result.rects?.length) {
|
|
623
|
+
addHighlight({
|
|
624
|
+
pageNumber: result.pageNumber,
|
|
625
|
+
text: result.text,
|
|
626
|
+
rects: result.rects,
|
|
627
|
+
color: 'yellow',
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
clearSearch();
|
|
632
|
+
setSearchQuery('');
|
|
349
633
|
};
|
|
350
634
|
|
|
351
|
-
|
|
352
|
-
|
|
635
|
+
// Add note at center of current page
|
|
636
|
+
const addNoteToCurrentPage = () => {
|
|
637
|
+
addNote({
|
|
638
|
+
pageNumber: currentPage,
|
|
639
|
+
x: 300,
|
|
640
|
+
y: 400,
|
|
641
|
+
content: 'New note',
|
|
642
|
+
color: '#ffeb3b',
|
|
643
|
+
});
|
|
353
644
|
};
|
|
354
645
|
|
|
355
646
|
return (
|
|
356
|
-
<div>
|
|
357
|
-
|
|
358
|
-
<
|
|
647
|
+
<div style={{ width: 300, padding: 16, borderRight: '1px solid #ccc' }}>
|
|
648
|
+
{/* Navigation */}
|
|
649
|
+
<div>
|
|
650
|
+
<h3>Navigation</h3>
|
|
651
|
+
<button onClick={() => goToPage(currentPage - 1)}>Previous</button>
|
|
652
|
+
<span> Page {currentPage} of {numPages} </span>
|
|
653
|
+
<button onClick={() => goToPage(currentPage + 1)}>Next</button>
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
{/* Search */}
|
|
657
|
+
<div>
|
|
658
|
+
<h3>Search</h3>
|
|
659
|
+
<input
|
|
660
|
+
value={searchQuery}
|
|
661
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
662
|
+
placeholder="Search..."
|
|
663
|
+
/>
|
|
664
|
+
<button onClick={handleSearch}>Search</button>
|
|
665
|
+
{searchResults.length > 0 && (
|
|
666
|
+
<div>
|
|
667
|
+
<p>Found {searchResults.length} matches</p>
|
|
668
|
+
<button onClick={highlightSearchResults}>
|
|
669
|
+
Highlight All
|
|
670
|
+
</button>
|
|
671
|
+
</div>
|
|
672
|
+
)}
|
|
673
|
+
</div>
|
|
674
|
+
|
|
675
|
+
{/* Highlights */}
|
|
676
|
+
<div>
|
|
677
|
+
<h3>Highlights ({allHighlights.length})</h3>
|
|
678
|
+
{allHighlights.map((h) => (
|
|
679
|
+
<div key={h.id}>
|
|
680
|
+
<span style={{ background: h.color }}>
|
|
681
|
+
Page {h.pageNumber}: {h.text.slice(0, 30)}...
|
|
682
|
+
</span>
|
|
683
|
+
<button onClick={() => deleteHighlight(h.id)}>×</button>
|
|
684
|
+
</div>
|
|
685
|
+
))}
|
|
686
|
+
</div>
|
|
687
|
+
|
|
688
|
+
{/* Annotations */}
|
|
689
|
+
<div>
|
|
690
|
+
<h3>Notes ({annotations.filter(a => a.type === 'note').length})</h3>
|
|
691
|
+
<button onClick={addNoteToCurrentPage}>Add Note</button>
|
|
692
|
+
</div>
|
|
359
693
|
</div>
|
|
360
694
|
);
|
|
361
695
|
}
|
|
362
|
-
```
|
|
363
696
|
|
|
364
|
-
|
|
697
|
+
export default App;
|
|
698
|
+
```
|
|
365
699
|
|
|
366
|
-
|
|
700
|
+
---
|
|
367
701
|
|
|
368
|
-
|
|
369
|
-
|-----------|-------------|
|
|
370
|
-
| `PDFViewerClient` | Full-featured viewer with built-in provider (easiest to use) |
|
|
371
|
-
| `PDFViewerProvider` | Context provider for using hooks |
|
|
372
|
-
| `DocumentContainer` | Single page view container |
|
|
373
|
-
| `ContinuousScrollContainer` | Continuous scroll view container |
|
|
374
|
-
| `DualPageContainer` | Dual page (book) view container |
|
|
375
|
-
| `Toolbar` | Navigation and zoom toolbar |
|
|
376
|
-
| `Sidebar` | Thumbnails, outline, search panel |
|
|
377
|
-
| `AnnotationToolbar` | Drawing and annotation tools |
|
|
702
|
+
## API Reference
|
|
378
703
|
|
|
379
704
|
### PDFViewerClient Props
|
|
380
705
|
|
|
@@ -383,221 +708,156 @@ function ExportImport() {
|
|
|
383
708
|
| `src` | `string \| ArrayBuffer` | required | PDF source URL or data |
|
|
384
709
|
| `showToolbar` | `boolean` | `true` | Show the toolbar |
|
|
385
710
|
| `showSidebar` | `boolean` | `true` | Show the sidebar |
|
|
386
|
-
| `
|
|
387
|
-
| `viewMode` | `'single' \| 'continuous' \| 'dual'` | `'single'` | Page view mode |
|
|
711
|
+
| `viewMode` | `'single' \| 'continuous' \| 'dual'` | `'continuous'` | Page view mode |
|
|
388
712
|
| `theme` | `'light' \| 'dark' \| 'sepia'` | `'light'` | Color theme |
|
|
389
713
|
| `initialPage` | `number` | `1` | Initial page to display |
|
|
390
714
|
| `initialScale` | `number` | `1` | Initial zoom scale |
|
|
391
715
|
| `onDocumentLoad` | `(event) => void` | - | Called when document loads |
|
|
392
716
|
| `onPageChange` | `(page) => void` | - | Called when page changes |
|
|
393
|
-
| `onScaleChange` | `(scale) => void` | - | Called when zoom changes |
|
|
394
717
|
| `onError` | `(error) => void` | - | Called on error |
|
|
395
718
|
|
|
396
|
-
###
|
|
397
|
-
|
|
398
|
-
#### `usePDFViewer()`
|
|
399
|
-
|
|
400
|
-
Main hook for viewer state and actions.
|
|
719
|
+
### usePDFViewer() Return Value
|
|
401
720
|
|
|
402
|
-
```
|
|
403
|
-
|
|
404
|
-
// Document
|
|
405
|
-
document
|
|
406
|
-
numPages
|
|
407
|
-
isLoading
|
|
408
|
-
error
|
|
721
|
+
```typescript
|
|
722
|
+
{
|
|
723
|
+
// Document
|
|
724
|
+
document: PDFDocumentProxy | null;
|
|
725
|
+
numPages: number;
|
|
726
|
+
isLoading: boolean;
|
|
727
|
+
error: Error | null;
|
|
728
|
+
loadDocument: (options: LoadOptions) => Promise<void>;
|
|
409
729
|
|
|
410
730
|
// Navigation
|
|
411
|
-
currentPage
|
|
412
|
-
goToPage
|
|
413
|
-
nextPage
|
|
414
|
-
previousPage
|
|
731
|
+
currentPage: number;
|
|
732
|
+
goToPage: (page: number) => void;
|
|
733
|
+
nextPage: () => void;
|
|
734
|
+
previousPage: () => void;
|
|
415
735
|
|
|
416
736
|
// Zoom
|
|
417
|
-
scale
|
|
418
|
-
setScale
|
|
419
|
-
zoomIn
|
|
420
|
-
zoomOut
|
|
421
|
-
fitToWidth
|
|
422
|
-
fitToPage
|
|
737
|
+
scale: number;
|
|
738
|
+
setScale: (scale: number) => void;
|
|
739
|
+
zoomIn: () => void;
|
|
740
|
+
zoomOut: () => void;
|
|
741
|
+
fitToWidth: () => void;
|
|
742
|
+
fitToPage: () => void;
|
|
423
743
|
|
|
424
744
|
// Rotation
|
|
425
|
-
rotation
|
|
426
|
-
rotateClockwise
|
|
745
|
+
rotation: number;
|
|
746
|
+
rotateClockwise: () => void;
|
|
747
|
+
rotateCounterClockwise: () => void;
|
|
748
|
+
|
|
749
|
+
// Theme
|
|
750
|
+
theme: 'light' | 'dark' | 'sepia';
|
|
751
|
+
setTheme: (theme: Theme) => void;
|
|
427
752
|
|
|
428
|
-
//
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
viewMode, // 'single' | 'continuous' | 'dual'
|
|
432
|
-
setViewMode, // (mode) => void
|
|
433
|
-
sidebarOpen, // Sidebar visibility
|
|
434
|
-
toggleSidebar, // () => void
|
|
753
|
+
// View mode
|
|
754
|
+
viewMode: 'single' | 'continuous' | 'dual';
|
|
755
|
+
setViewMode: (mode: ViewMode) => void;
|
|
435
756
|
|
|
436
757
|
// Search
|
|
437
|
-
search
|
|
438
|
-
searchResults
|
|
439
|
-
|
|
440
|
-
|
|
758
|
+
search: (query: string) => Promise<void>;
|
|
759
|
+
searchResults: SearchResult[];
|
|
760
|
+
currentSearchResult: number;
|
|
761
|
+
nextSearchResult: () => void;
|
|
762
|
+
previousSearchResult: () => void;
|
|
763
|
+
clearSearch: () => void;
|
|
764
|
+
|
|
765
|
+
// Highlights
|
|
766
|
+
highlights: Highlight[];
|
|
767
|
+
addHighlight: (params: AddHighlightParams) => Highlight;
|
|
768
|
+
removeHighlight: (id: string) => void;
|
|
769
|
+
}
|
|
441
770
|
```
|
|
442
771
|
|
|
443
|
-
|
|
772
|
+
### Coordinate System
|
|
444
773
|
|
|
445
|
-
|
|
774
|
+
PDF coordinates use **points** (1 point = 1/72 inch):
|
|
775
|
+
- Origin (0, 0) is at the **top-left** corner
|
|
776
|
+
- X increases to the right
|
|
777
|
+
- Y increases downward
|
|
778
|
+
- Standard US Letter: 612 × 792 points (8.5" × 11")
|
|
446
779
|
|
|
447
780
|
```tsx
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
selectedHighlight, // Current selection
|
|
452
|
-
activeColor, // Active highlight color
|
|
453
|
-
|
|
454
|
-
// Actions
|
|
455
|
-
addHighlight, // Add highlight with coordinates
|
|
456
|
-
createHighlightFromSelection, // Create from text selection
|
|
457
|
-
updateHighlight, // (id, updates) => void
|
|
458
|
-
deleteHighlight, // (id) => void
|
|
459
|
-
selectHighlight, // (id) => void
|
|
460
|
-
setActiveColor, // (color) => void
|
|
461
|
-
highlightsForPage, // (pageNumber) => Highlight[]
|
|
462
|
-
} = useHighlights({
|
|
463
|
-
onHighlightCreate: (highlight) => {},
|
|
464
|
-
onHighlightUpdate: (highlight) => {},
|
|
465
|
-
onHighlightDelete: (id) => {},
|
|
466
|
-
});
|
|
781
|
+
// Place element 1 inch from left, 2 inches from top
|
|
782
|
+
const x = 72; // 1 inch × 72 points/inch
|
|
783
|
+
const y = 144; // 2 inches × 72 points/inch
|
|
467
784
|
```
|
|
468
785
|
|
|
469
|
-
|
|
786
|
+
---
|
|
470
787
|
|
|
471
|
-
|
|
788
|
+
## Additional Features
|
|
789
|
+
|
|
790
|
+
### Themes
|
|
472
791
|
|
|
473
792
|
```tsx
|
|
474
|
-
const {
|
|
475
|
-
// State
|
|
476
|
-
annotations, // Annotation[]
|
|
477
|
-
selectedAnnotation, // Current selection
|
|
478
|
-
activeTool, // 'note' | 'draw' | 'shape' | null
|
|
479
|
-
activeShapeType, // 'rect' | 'circle' | 'arrow' | 'line'
|
|
480
|
-
drawingColor, // Current drawing color
|
|
481
|
-
drawingStrokeWidth, // Current stroke width
|
|
482
|
-
|
|
483
|
-
// Tool actions
|
|
484
|
-
setActiveTool, // (tool) => void
|
|
485
|
-
setActiveShapeType, // (type) => void
|
|
486
|
-
setDrawingColor, // (color) => void
|
|
487
|
-
setDrawingStrokeWidth, // (width) => void
|
|
488
|
-
|
|
489
|
-
// Note actions
|
|
490
|
-
createNote, // (page, x, y, content?, color?) => Note
|
|
491
|
-
updateNote, // (id, updates) => void
|
|
492
|
-
|
|
493
|
-
// Shape actions
|
|
494
|
-
createShape, // (options) => Shape
|
|
495
|
-
updateShape, // (id, updates) => void
|
|
496
|
-
|
|
497
|
-
// Drawing actions
|
|
498
|
-
startDrawing, // (page, point) => void
|
|
499
|
-
continueDrawing, // (point) => void
|
|
500
|
-
finishDrawing, // () => Annotation | null
|
|
501
|
-
|
|
502
|
-
// General
|
|
503
|
-
selectAnnotation, // (id) => void
|
|
504
|
-
deleteAnnotation, // (id) => void
|
|
505
|
-
getAnnotationsByPage, // (page) => Annotation[]
|
|
506
|
-
exportAnnotations, // () => string (JSON)
|
|
507
|
-
importAnnotations, // (json) => void
|
|
508
|
-
} = useAnnotations({
|
|
509
|
-
onAnnotationCreate: (annotation) => {},
|
|
510
|
-
onAnnotationUpdate: (annotation) => {},
|
|
511
|
-
onAnnotationDelete: (id) => {},
|
|
512
|
-
});
|
|
513
|
-
```
|
|
793
|
+
const { theme, setTheme } = usePDFViewer();
|
|
514
794
|
|
|
515
|
-
|
|
795
|
+
setTheme('light'); // Light background
|
|
796
|
+
setTheme('dark'); // Dark background
|
|
797
|
+
setTheme('sepia'); // Sepia/warm background
|
|
798
|
+
```
|
|
516
799
|
|
|
517
|
-
###
|
|
800
|
+
### View Modes
|
|
518
801
|
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
id: string;
|
|
522
|
-
pageNumber: number;
|
|
523
|
-
rects: HighlightRect[];
|
|
524
|
-
text: string;
|
|
525
|
-
color: 'yellow' | 'green' | 'blue' | 'pink' | 'orange';
|
|
526
|
-
comment?: string;
|
|
527
|
-
createdAt: Date;
|
|
528
|
-
updatedAt: Date;
|
|
529
|
-
}
|
|
802
|
+
```tsx
|
|
803
|
+
const { viewMode, setViewMode } = usePDFViewer();
|
|
530
804
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
width: number; // Width in PDF points
|
|
535
|
-
height: number; // Height in PDF points
|
|
536
|
-
}
|
|
805
|
+
setViewMode('single'); // One page at a time
|
|
806
|
+
setViewMode('continuous'); // Scrollable pages (virtualized)
|
|
807
|
+
setViewMode('dual'); // Two pages side by side
|
|
537
808
|
```
|
|
538
809
|
|
|
539
|
-
###
|
|
810
|
+
### Document Outline
|
|
540
811
|
|
|
541
|
-
```
|
|
542
|
-
|
|
812
|
+
```tsx
|
|
813
|
+
import { getOutline } from 'pdfjs-reader-core';
|
|
543
814
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
pageNumber: number;
|
|
548
|
-
x: number;
|
|
549
|
-
y: number;
|
|
550
|
-
content: string;
|
|
551
|
-
color: string;
|
|
552
|
-
createdAt: Date;
|
|
553
|
-
updatedAt: Date;
|
|
554
|
-
}
|
|
815
|
+
const outline = await getOutline(document);
|
|
816
|
+
// Returns table of contents structure
|
|
817
|
+
```
|
|
555
818
|
|
|
556
|
-
|
|
557
|
-
id: string;
|
|
558
|
-
type: 'shape';
|
|
559
|
-
pageNumber: number;
|
|
560
|
-
shapeType: 'rect' | 'circle' | 'arrow' | 'line';
|
|
561
|
-
x: number;
|
|
562
|
-
y: number;
|
|
563
|
-
width: number;
|
|
564
|
-
height: number;
|
|
565
|
-
color: string;
|
|
566
|
-
strokeWidth: number;
|
|
567
|
-
createdAt: Date;
|
|
568
|
-
updatedAt: Date;
|
|
569
|
-
}
|
|
819
|
+
### Export Annotations
|
|
570
820
|
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
821
|
+
```tsx
|
|
822
|
+
import {
|
|
823
|
+
exportHighlightsAsJSON,
|
|
824
|
+
exportHighlightsAsMarkdown,
|
|
825
|
+
downloadAnnotationsAsMarkdown,
|
|
826
|
+
} from 'pdfjs-reader-core';
|
|
827
|
+
|
|
828
|
+
// Export highlights as JSON
|
|
829
|
+
exportHighlightsAsJSON(highlights, 'highlights.json');
|
|
830
|
+
|
|
831
|
+
// Export as readable Markdown
|
|
832
|
+
downloadAnnotationsAsMarkdown({
|
|
833
|
+
highlights,
|
|
834
|
+
documentTitle: 'My Document',
|
|
835
|
+
}, 'notes.md');
|
|
581
836
|
```
|
|
582
837
|
|
|
583
|
-
|
|
838
|
+
---
|
|
584
839
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
-
|
|
840
|
+
## Performance
|
|
841
|
+
|
|
842
|
+
The library is optimized for fast rendering:
|
|
843
|
+
|
|
844
|
+
- **Virtualization** - Only visible pages are rendered
|
|
845
|
+
- **Range requests** - Downloads only needed PDF data
|
|
846
|
+
- **Page caching** - Loaded pages are cached
|
|
847
|
+
- **Full quality** - Renders at device pixel ratio for crisp text
|
|
590
848
|
|
|
591
|
-
Example: To place a highlight 1 inch from the left and 2 inches from the top:
|
|
592
849
|
```tsx
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
850
|
+
import { loadDocument, preloadDocument, clearDocumentCache } from 'pdfjs-reader-core';
|
|
851
|
+
|
|
852
|
+
// Preload next document
|
|
853
|
+
await preloadDocument('/next-doc.pdf');
|
|
854
|
+
|
|
855
|
+
// Clear cache to free memory
|
|
856
|
+
clearDocumentCache('/doc.pdf');
|
|
599
857
|
```
|
|
600
858
|
|
|
859
|
+
---
|
|
860
|
+
|
|
601
861
|
## Browser Support
|
|
602
862
|
|
|
603
863
|
- Chrome (recommended)
|
|
@@ -608,8 +868,3 @@ addHighlight({
|
|
|
608
868
|
## License
|
|
609
869
|
|
|
610
870
|
MIT
|
|
611
|
-
|
|
612
|
-
## Links
|
|
613
|
-
|
|
614
|
-
- [GitHub Repository](https://github.com/suhasTeju/pdf-reader-js)
|
|
615
|
-
- [Report Issues](https://github.com/suhasTeju/pdf-reader-js/issues)
|