pdfjs-reader-core 0.1.0 → 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 ADDED
@@ -0,0 +1,870 @@
1
+ # pdfjs-reader-core
2
+
3
+ A React library for rendering PDFs with built-in search, highlighting, and annotation capabilities.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install pdfjs-reader-core
9
+ # or
10
+ yarn add pdfjs-reader-core
11
+ # or
12
+ pnpm add pdfjs-reader-core
13
+ ```
14
+
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:
22
+
23
+ ```tsx
24
+ import { PDFViewerClient } from 'pdfjs-reader-core';
25
+ import 'pdfjs-reader-core/styles.css';
26
+
27
+ function App() {
28
+ return (
29
+ <div style={{ height: '100vh' }}>
30
+ <PDFViewerClient
31
+ src="/document.pdf"
32
+ showToolbar
33
+ showSidebar
34
+ onDocumentLoad={({ numPages }) => console.log(`Loaded ${numPages} pages`)}
35
+ onError={(error) => console.error('Failed to load:', error)}
36
+ />
37
+ </div>
38
+ );
39
+ }
40
+ ```
41
+
42
+ ### Custom Viewer with Hooks
43
+
44
+ For more control, use the provider and hooks:
45
+
46
+ ```tsx
47
+ import {
48
+ PDFViewerProvider,
49
+ usePDFViewer,
50
+ ContinuousScrollContainer,
51
+ Toolbar,
52
+ Sidebar,
53
+ } from 'pdfjs-reader-core';
54
+ import 'pdfjs-reader-core/styles.css';
55
+
56
+ function App() {
57
+ return (
58
+ <PDFViewerProvider>
59
+ <MyPDFViewer />
60
+ </PDFViewerProvider>
61
+ );
62
+ }
63
+
64
+ function MyPDFViewer() {
65
+ const { loadDocument, isLoading, error, numPages } = usePDFViewer();
66
+
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
+
74
+ return (
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>
82
+ );
83
+ }
84
+ ```
85
+
86
+ ### Load PDF from Different Sources
87
+
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
114
+
115
+ ```tsx
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();
130
+
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
+ ```
138
+
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
+ }
210
+ };
211
+
212
+ return (
213
+ <div>
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
+ )}
231
+ </div>
232
+ );
233
+ }
234
+ ```
235
+
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);
268
+ ```
269
+
270
+ ### Highlight from Search Results
271
+
272
+ Convert search results into permanent highlights:
273
+
274
+ ```tsx
275
+ const { search, searchResults, addHighlight } = usePDFViewer();
276
+
277
+ // Search for a term
278
+ await search('important');
279
+
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',
288
+ });
289
+ }
290
+ });
291
+ ```
292
+
293
+ ### Using the useHighlights Hook
294
+
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!' });
324
+
325
+ return (
326
+ <div>
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
+ ))}
334
+ </div>
335
+ );
336
+ }
337
+ ```
338
+
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
356
+
357
+ Save and restore highlights:
358
+
359
+ ```tsx
360
+ import {
361
+ saveHighlights,
362
+ loadHighlights,
363
+ exportHighlightsAsJSON,
364
+ importHighlightsFromJSON,
365
+ } from 'pdfjs-reader-core';
366
+
367
+ // Save to localStorage
368
+ saveHighlights('doc-123', highlights);
369
+
370
+ // Load from localStorage
371
+ const saved = loadHighlights('doc-123');
372
+
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
+ });
405
+ };
406
+
407
+ // List all notes
408
+ const notes = annotations.filter(a => a.type === 'note');
409
+
410
+ return (
411
+ <div>
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
+ ))}
419
+ </div>
420
+ );
421
+ }
422
+ ```
423
+
424
+ ### Add Shapes
425
+
426
+ ```tsx
427
+ const addShape = useAnnotationStore((s) => s.addShape);
428
+
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
+ });
440
+
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
+ });
452
+
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
+ });
464
+
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
+ ```
477
+
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();
501
+ ```
502
+
503
+ ### Enable Drawing Mode UI
504
+
505
+ ```tsx
506
+ const setActiveAnnotationTool = useAnnotationStore((s) => s.setActiveAnnotationTool);
507
+ const activeAnnotationTool = useAnnotationStore((s) => s.activeAnnotationTool);
508
+
509
+ // Enable drawing mode
510
+ setActiveAnnotationTool('draw');
511
+
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');
524
+ }
525
+ ```
526
+
527
+ ### Annotation Type Definition
528
+
529
+ ```typescript
530
+ interface Annotation {
531
+ id: string;
532
+ pageNumber: number;
533
+ type: 'note' | 'drawing' | 'shape';
534
+
535
+ // For notes
536
+ content?: string;
537
+ x?: number;
538
+ y?: number;
539
+
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;
553
+ }
554
+ ```
555
+
556
+ ---
557
+
558
+ ## 5. Complete Example
559
+
560
+ Here's a full example combining rendering, search, highlighting, and annotations:
561
+
562
+ ```tsx
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';
572
+
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();
599
+
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('');
633
+ };
634
+
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
+ });
644
+ };
645
+
646
+ return (
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>
693
+ </div>
694
+ );
695
+ }
696
+
697
+ export default App;
698
+ ```
699
+
700
+ ---
701
+
702
+ ## API Reference
703
+
704
+ ### PDFViewerClient Props
705
+
706
+ | Prop | Type | Default | Description |
707
+ |------|------|---------|-------------|
708
+ | `src` | `string \| ArrayBuffer` | required | PDF source URL or data |
709
+ | `showToolbar` | `boolean` | `true` | Show the toolbar |
710
+ | `showSidebar` | `boolean` | `true` | Show the sidebar |
711
+ | `viewMode` | `'single' \| 'continuous' \| 'dual'` | `'continuous'` | Page view mode |
712
+ | `theme` | `'light' \| 'dark' \| 'sepia'` | `'light'` | Color theme |
713
+ | `initialPage` | `number` | `1` | Initial page to display |
714
+ | `initialScale` | `number` | `1` | Initial zoom scale |
715
+ | `onDocumentLoad` | `(event) => void` | - | Called when document loads |
716
+ | `onPageChange` | `(page) => void` | - | Called when page changes |
717
+ | `onError` | `(error) => void` | - | Called on error |
718
+
719
+ ### usePDFViewer() Return Value
720
+
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>;
729
+
730
+ // Navigation
731
+ currentPage: number;
732
+ goToPage: (page: number) => void;
733
+ nextPage: () => void;
734
+ previousPage: () => void;
735
+
736
+ // Zoom
737
+ scale: number;
738
+ setScale: (scale: number) => void;
739
+ zoomIn: () => void;
740
+ zoomOut: () => void;
741
+ fitToWidth: () => void;
742
+ fitToPage: () => void;
743
+
744
+ // Rotation
745
+ rotation: number;
746
+ rotateClockwise: () => void;
747
+ rotateCounterClockwise: () => void;
748
+
749
+ // Theme
750
+ theme: 'light' | 'dark' | 'sepia';
751
+ setTheme: (theme: Theme) => void;
752
+
753
+ // View mode
754
+ viewMode: 'single' | 'continuous' | 'dual';
755
+ setViewMode: (mode: ViewMode) => void;
756
+
757
+ // Search
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
+ }
770
+ ```
771
+
772
+ ### Coordinate System
773
+
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")
779
+
780
+ ```tsx
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
784
+ ```
785
+
786
+ ---
787
+
788
+ ## Additional Features
789
+
790
+ ### Themes
791
+
792
+ ```tsx
793
+ const { theme, setTheme } = usePDFViewer();
794
+
795
+ setTheme('light'); // Light background
796
+ setTheme('dark'); // Dark background
797
+ setTheme('sepia'); // Sepia/warm background
798
+ ```
799
+
800
+ ### View Modes
801
+
802
+ ```tsx
803
+ const { viewMode, setViewMode } = usePDFViewer();
804
+
805
+ setViewMode('single'); // One page at a time
806
+ setViewMode('continuous'); // Scrollable pages (virtualized)
807
+ setViewMode('dual'); // Two pages side by side
808
+ ```
809
+
810
+ ### Document Outline
811
+
812
+ ```tsx
813
+ import { getOutline } from 'pdfjs-reader-core';
814
+
815
+ const outline = await getOutline(document);
816
+ // Returns table of contents structure
817
+ ```
818
+
819
+ ### Export Annotations
820
+
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');
836
+ ```
837
+
838
+ ---
839
+
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
848
+
849
+ ```tsx
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');
857
+ ```
858
+
859
+ ---
860
+
861
+ ## Browser Support
862
+
863
+ - Chrome (recommended)
864
+ - Firefox
865
+ - Safari
866
+ - Edge
867
+
868
+ ## License
869
+
870
+ MIT