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 CHANGED
@@ -1,18 +1,6 @@
1
1
  # pdfjs-reader-core
2
2
 
3
- A fully-featured, Next.js-compatible PDF viewer for React with annotations, highlights, search, and more.
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
- ## Quick Start
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
- ## Usage Examples
42
+ ### Custom Viewer with Hooks
47
43
 
48
- ### Basic Viewer
44
+ For more control, use the provider and hooks:
49
45
 
50
46
  ```tsx
51
- import { PDFViewerClient } from 'pdfjs-reader-core';
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 BasicViewer() {
56
+ function App() {
55
57
  return (
56
- <PDFViewerClient
57
- src="https://example.com/document.pdf"
58
- showToolbar={true}
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
- ### With Annotations Toolbar
64
+ function MyPDFViewer() {
65
+ const { loadDocument, isLoading, error, numPages } = usePDFViewer();
69
66
 
70
- ```tsx
71
- import { PDFViewerClient } from 'pdfjs-reader-core';
72
- import 'pdfjs-reader-core/styles.css';
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
- <PDFViewerClient
77
- src="/document.pdf"
78
- showToolbar
79
- showSidebar
80
- showAnnotationToolbar={true} // Enables annotation tools
81
- viewMode="continuous" // continuous | single | dual
82
- theme="dark"
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
- ### Programmatic Highlights
86
+ ### Load PDF from Different Sources
89
87
 
90
- Add highlights programmatically using coordinates:
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
- import {
94
- PDFViewerProvider,
95
- useHighlights,
96
- usePDFViewer,
97
- DocumentContainer,
98
- Toolbar
99
- } from 'pdfjs-reader-core';
100
- import 'pdfjs-reader-core/styles.css';
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
- function HighlightDemo() {
103
- const { addHighlight, allHighlights } = useHighlights();
104
- const { currentPage } = usePDFViewer();
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
- const handleAddHighlight = () => {
107
- addHighlight({
108
- pageNumber: currentPage,
109
- rects: [
110
- { x: 72, y: 100, width: 200, height: 14 },
111
- { x: 72, y: 116, width: 150, height: 14 },
112
- ],
113
- text: 'Highlighted text',
114
- color: 'yellow', // yellow | green | blue | pink | orange
115
- comment: 'My note',
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
- <button onClick={handleAddHighlight}>Add Highlight</button>
122
- <p>Total highlights: {allHighlights.length}</p>
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
- // Wrap with provider
128
- function App() {
129
- return (
130
- <PDFViewerProvider>
131
- <Toolbar />
132
- <DocumentContainer />
133
- <HighlightDemo />
134
- </PDFViewerProvider>
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
- ### Programmatic Shape Annotations
270
+ ### Highlight from Search Results
140
271
 
141
- Add shapes (rectangles, circles, arrows, lines) programmatically:
272
+ Convert search results into permanent highlights:
142
273
 
143
274
  ```tsx
144
- import { useAnnotations, usePDFViewer } from 'pdfjs-reader-core';
275
+ const { search, searchResults, addHighlight } = usePDFViewer();
145
276
 
146
- function ShapeDemo() {
147
- const { createShape, annotations } = useAnnotations();
148
- const { currentPage } = usePDFViewer();
277
+ // Search for a term
278
+ await search('important');
149
279
 
150
- const addRectangle = () => {
151
- createShape({
152
- pageNumber: currentPage,
153
- shapeType: 'rect', // rect | circle | arrow | line
154
- x: 100,
155
- y: 200,
156
- width: 150,
157
- height: 80,
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
- const addCircle = () => {
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
- const addArrow = () => {
177
- createShape({
178
- pageNumber: currentPage,
179
- shapeType: 'arrow',
180
- x: 100,
181
- y: 350,
182
- width: 120,
183
- height: 40,
184
- color: '#22c55e',
185
- strokeWidth: 3,
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
- <button onClick={addRectangle}>Add Rectangle</button>
192
- <button onClick={addCircle}>Add Circle</button>
193
- <button onClick={addArrow}>Add Arrow</button>
194
- <p>Total annotations: {annotations.length}</p>
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
- ### Search and Highlight Text
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
- Search for text and highlight all matches:
357
+ Save and restore highlights:
203
358
 
204
359
  ```tsx
205
- import { usePDFViewer, useHighlights } from 'pdfjs-reader-core';
360
+ import {
361
+ saveHighlights,
362
+ loadHighlights,
363
+ exportHighlightsAsJSON,
364
+ importHighlightsFromJSON,
365
+ } from 'pdfjs-reader-core';
206
366
 
207
- function SearchAndHighlight() {
208
- const { search, searchResults, goToPage } = usePDFViewer();
209
- const { addHighlight } = useHighlights();
210
- const [query, setQuery] = useState('');
367
+ // Save to localStorage
368
+ saveHighlights('doc-123', highlights);
211
369
 
212
- const handleSearch = async () => {
213
- await search(query);
214
- };
370
+ // Load from localStorage
371
+ const saved = loadHighlights('doc-123');
215
372
 
216
- const highlightAllMatches = () => {
217
- for (const result of searchResults) {
218
- if (result.rects?.length > 0) {
219
- addHighlight({
220
- pageNumber: result.pageNumber,
221
- rects: result.rects,
222
- text: result.text,
223
- color: 'yellow',
224
- });
225
- }
226
- }
227
- // Navigate to first match
228
- if (searchResults.length > 0) {
229
- goToPage(searchResults[0].pageNumber);
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
- <input
236
- value={query}
237
- onChange={(e) => setQuery(e.target.value)}
238
- placeholder="Search text..."
239
- />
240
- <button onClick={handleSearch}>Search</button>
241
- <button onClick={highlightAllMatches}>
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
- ### Navigation and Zoom Controls
424
+ ### Add Shapes
250
425
 
251
426
  ```tsx
252
- import { usePDFViewer } from 'pdfjs-reader-core';
427
+ const addShape = useAnnotationStore((s) => s.addShape);
253
428
 
254
- function CustomControls() {
255
- const {
256
- currentPage,
257
- numPages,
258
- scale,
259
- goToPage,
260
- nextPage,
261
- previousPage,
262
- zoomIn,
263
- zoomOut,
264
- setScale,
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
- return (
271
- <div>
272
- {/* Navigation */}
273
- <button onClick={previousPage} disabled={currentPage <= 1}>Previous</button>
274
- <span>{currentPage} / {numPages}</span>
275
- <button onClick={nextPage} disabled={currentPage >= numPages}>Next</button>
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
- {/* Jump to page */}
278
- <input
279
- type="number"
280
- min={1}
281
- max={numPages}
282
- value={currentPage}
283
- onChange={(e) => goToPage(parseInt(e.target.value))}
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
- {/* Zoom */}
287
- <button onClick={zoomOut}>-</button>
288
- <span>{Math.round(scale * 100)}%</span>
289
- <button onClick={zoomIn}>+</button>
290
- <button onClick={fitToWidth}>Fit Width</button>
291
- <button onClick={fitToPage}>Fit Page</button>
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
- {/* Rotation */}
294
- <button onClick={rotateClockwise}>Rotate</button>
295
- </div>
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
- ### Theme Switching
503
+ ### Enable Drawing Mode UI
301
504
 
302
505
  ```tsx
303
- import { usePDFViewer } from 'pdfjs-reader-core';
506
+ const setActiveAnnotationTool = useAnnotationStore((s) => s.setActiveAnnotationTool);
507
+ const activeAnnotationTool = useAnnotationStore((s) => s.activeAnnotationTool);
304
508
 
305
- function ThemeSwitcher() {
306
- const { theme, setTheme } = usePDFViewer();
509
+ // Enable drawing mode
510
+ setActiveAnnotationTool('draw');
307
511
 
308
- return (
309
- <select value={theme} onChange={(e) => setTheme(e.target.value)}>
310
- <option value="light">Light</option>
311
- <option value="dark">Dark</option>
312
- <option value="sepia">Sepia</option>
313
- </select>
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
- ### View Mode Switching
527
+ ### Annotation Type Definition
319
528
 
320
- ```tsx
321
- import { usePDFViewer } from 'pdfjs-reader-core';
529
+ ```typescript
530
+ interface Annotation {
531
+ id: string;
532
+ pageNumber: number;
533
+ type: 'note' | 'drawing' | 'shape';
322
534
 
323
- function ViewModeSwitcher() {
324
- const { viewMode, setViewMode } = usePDFViewer();
535
+ // For notes
536
+ content?: string;
537
+ x?: number;
538
+ y?: number;
325
539
 
326
- return (
327
- <select value={viewMode} onChange={(e) => setViewMode(e.target.value)}>
328
- <option value="single">Single Page</option>
329
- <option value="continuous">Continuous Scroll</option>
330
- <option value="dual">Dual Page</option>
331
- </select>
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
- ### Export/Import Annotations
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 { useAnnotations, useHighlights } from 'pdfjs-reader-core';
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 ExportImport() {
342
- const { exportAnnotations, importAnnotations } = useAnnotations();
343
- const { allHighlights } = useHighlights();
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
- const handleExport = () => {
346
- const json = exportAnnotations();
347
- // Save to file or send to server
348
- console.log(json);
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
- const handleImport = (jsonString: string) => {
352
- importAnnotations(jsonString);
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
- <button onClick={handleExport}>Export Annotations</button>
358
- <button onClick={() => handleImport('[]')}>Import Annotations</button>
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
- ## API Reference
697
+ export default App;
698
+ ```
365
699
 
366
- ### Components
700
+ ---
367
701
 
368
- | Component | Description |
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
- | `showAnnotationToolbar` | `boolean` | `false` | Show annotation tools |
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
- ### Hooks
397
-
398
- #### `usePDFViewer()`
399
-
400
- Main hook for viewer state and actions.
719
+ ### usePDFViewer() Return Value
401
720
 
402
- ```tsx
403
- const {
404
- // Document state
405
- document, // PDFDocumentProxy
406
- numPages, // Total pages
407
- isLoading, // Loading state
408
- error, // Error state
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, // Current page number
412
- goToPage, // (page: number) => void
413
- nextPage, // () => void
414
- previousPage, // () => void
731
+ currentPage: number;
732
+ goToPage: (page: number) => void;
733
+ nextPage: () => void;
734
+ previousPage: () => void;
415
735
 
416
736
  // Zoom
417
- scale, // Current zoom level
418
- setScale, // (scale: number) => void
419
- zoomIn, // () => void
420
- zoomOut, // () => void
421
- fitToWidth, // () => void
422
- fitToPage, // () => void
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, // Current rotation (0, 90, 180, 270)
426
- rotateClockwise, // () => void
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
- // Theme & UI
429
- theme, // 'light' | 'dark' | 'sepia'
430
- setTheme, // (theme) => void
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, // (query: string) => Promise<void>
438
- searchResults, // SearchResult[]
439
- clearSearch, // () => void
440
- } = usePDFViewer();
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
- #### `useHighlights(options?)`
772
+ ### Coordinate System
444
773
 
445
- Hook for managing text highlights.
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
- const {
449
- // State
450
- allHighlights, // Highlight[]
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
- #### `useAnnotations(options?)`
786
+ ---
470
787
 
471
- Hook for managing annotations (notes, drawings, shapes).
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
- ## Types
795
+ setTheme('light'); // Light background
796
+ setTheme('dark'); // Dark background
797
+ setTheme('sepia'); // Sepia/warm background
798
+ ```
516
799
 
517
- ### Highlight
800
+ ### View Modes
518
801
 
519
- ```typescript
520
- interface Highlight {
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
- interface HighlightRect {
532
- x: number; // PDF points from left
533
- y: number; // PDF points from top
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
- ### Annotation
810
+ ### Document Outline
540
811
 
541
- ```typescript
542
- type Annotation = NoteAnnotation | DrawingAnnotation | ShapeAnnotation;
812
+ ```tsx
813
+ import { getOutline } from 'pdfjs-reader-core';
543
814
 
544
- interface NoteAnnotation {
545
- id: string;
546
- type: 'note';
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
- interface ShapeAnnotation {
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
- interface DrawingAnnotation {
572
- id: string;
573
- type: 'drawing';
574
- pageNumber: number;
575
- paths: { points: { x: number; y: number }[] }[];
576
- color: string;
577
- strokeWidth: number;
578
- createdAt: Date;
579
- updatedAt: Date;
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
- ## Coordinate System
838
+ ---
584
839
 
585
- PDF coordinates are in **points** (1 point = 1/72 inch):
586
- - Origin (0, 0) is at the **top-left** corner
587
- - X increases to the right
588
- - Y increases downward
589
- - Standard US Letter page: 612 x 792 points
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
- addHighlight({
594
- pageNumber: 1,
595
- rects: [{ x: 72, y: 144, width: 100, height: 14 }],
596
- text: 'Example',
597
- color: 'yellow',
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)