hazo_pdf 1.0.0 → 1.0.1
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 +553 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# Hazo PDF
|
|
2
2
|
|
|
3
|
-
A React component library for viewing and annotating PDF documents.
|
|
3
|
+
A React component library for viewing and annotating PDF documents with support for Square annotations, FreeText annotations, custom stamps, timestamps, and comprehensive styling customization.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
-
- PDF
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
7
|
+
- 📄 **PDF Viewing** - Render PDF documents with customizable zoom levels
|
|
8
|
+
- ✏️ **Annotations** - Square and FreeText annotation tools
|
|
9
|
+
- 🎨 **Customizable Styling** - Extensive configuration options via INI file
|
|
10
|
+
- ⏰ **Timestamp Support** - Automatic timestamp appending to annotations
|
|
11
|
+
- 🏷️ **Custom Stamps** - Add quick-insert stamps via right-click menu
|
|
12
|
+
- 💾 **Annotation Persistence** - Save annotations directly into PDF files
|
|
13
|
+
- 🎯 **Pan Tool** - Default pan/scroll mode for document navigation
|
|
14
|
+
- ↪️ **Undo/Redo** - Full annotation history management
|
|
11
15
|
|
|
12
16
|
## Installation
|
|
13
17
|
|
|
@@ -15,7 +19,7 @@ A React component library for viewing and annotating PDF documents.
|
|
|
15
19
|
npm install hazo_pdf
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
##
|
|
22
|
+
## Quick Start
|
|
19
23
|
|
|
20
24
|
```tsx
|
|
21
25
|
import { PdfViewer } from 'hazo_pdf';
|
|
@@ -30,6 +34,535 @@ function App() {
|
|
|
30
34
|
}
|
|
31
35
|
```
|
|
32
36
|
|
|
37
|
+
That's it! The PDF viewer will load and display your document with default styling and pan mode enabled.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Examples
|
|
42
|
+
|
|
43
|
+
### Simple Example - Basic PDF Viewer
|
|
44
|
+
|
|
45
|
+
The simplest usage - just display a PDF:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { PdfViewer } from 'hazo_pdf';
|
|
49
|
+
import 'hazo_pdf/styles.css';
|
|
50
|
+
|
|
51
|
+
function SimpleViewer() {
|
|
52
|
+
return (
|
|
53
|
+
<div style={{ width: '100%', height: '800px' }}>
|
|
54
|
+
<PdfViewer
|
|
55
|
+
url="/api/documents/sample.pdf"
|
|
56
|
+
className="h-full w-full"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Features demonstrated:**
|
|
64
|
+
- Basic PDF rendering
|
|
65
|
+
- Default pan tool (drag to scroll)
|
|
66
|
+
- Default styling
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
### Medium Complexity Example - With Annotations and Callbacks
|
|
71
|
+
|
|
72
|
+
A more feature-rich implementation with annotation handling:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { useState } from 'react';
|
|
76
|
+
import { PdfViewer } from 'hazo_pdf';
|
|
77
|
+
import type { PdfAnnotation, PDFDocumentProxy } from 'hazo_pdf';
|
|
78
|
+
import 'hazo_pdf/styles.css';
|
|
79
|
+
|
|
80
|
+
function AnnotatedViewer() {
|
|
81
|
+
const [annotations, setAnnotations] = useState<PdfAnnotation[]>([]);
|
|
82
|
+
const [pdfDoc, setPdfDoc] = useState<PDFDocumentProxy | null>(null);
|
|
83
|
+
|
|
84
|
+
const handleLoad = (pdf: PDFDocumentProxy) => {
|
|
85
|
+
console.log('PDF loaded:', pdf.numPages, 'pages');
|
|
86
|
+
setPdfDoc(pdf);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleAnnotationCreate = (annotation: PdfAnnotation) => {
|
|
90
|
+
console.log('Annotation created:', annotation);
|
|
91
|
+
setAnnotations(prev => [...prev, annotation]);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleAnnotationUpdate = (annotation: PdfAnnotation) => {
|
|
95
|
+
console.log('Annotation updated:', annotation);
|
|
96
|
+
setAnnotations(prev =>
|
|
97
|
+
prev.map(a => a.id === annotation.id ? annotation : a)
|
|
98
|
+
);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const handleAnnotationDelete = (annotationId: string) => {
|
|
102
|
+
console.log('Annotation deleted:', annotationId);
|
|
103
|
+
setAnnotations(prev => prev.filter(a => a.id !== annotationId));
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const handleSave = (pdfBytes: Uint8Array, filename: string) => {
|
|
107
|
+
// Create a blob and download
|
|
108
|
+
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
|
109
|
+
const url = URL.createObjectURL(blob);
|
|
110
|
+
const a = document.createElement('a');
|
|
111
|
+
a.href = url;
|
|
112
|
+
a.download = filename || 'annotated-document.pdf';
|
|
113
|
+
a.click();
|
|
114
|
+
URL.revokeObjectURL(url);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const handleError = (error: Error) => {
|
|
118
|
+
console.error('PDF error:', error);
|
|
119
|
+
alert(`Failed to load PDF: ${error.message}`);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<div style={{ width: '100%', height: '100vh' }}>
|
|
124
|
+
<PdfViewer
|
|
125
|
+
url="/api/documents/report.pdf"
|
|
126
|
+
className="h-full w-full"
|
|
127
|
+
scale={1.2}
|
|
128
|
+
annotations={annotations}
|
|
129
|
+
on_load={handleLoad}
|
|
130
|
+
on_error={handleError}
|
|
131
|
+
on_annotation_create={handleAnnotationCreate}
|
|
132
|
+
on_annotation_update={handleAnnotationUpdate}
|
|
133
|
+
on_annotation_delete={handleAnnotationDelete}
|
|
134
|
+
on_save={handleSave}
|
|
135
|
+
background_color="#f5f5f5"
|
|
136
|
+
/>
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Features demonstrated:**
|
|
143
|
+
- Annotation state management
|
|
144
|
+
- Callback handlers for all events
|
|
145
|
+
- Custom zoom level (1.2x)
|
|
146
|
+
- Custom background color
|
|
147
|
+
- PDF download with annotations
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
### Complex Example - Full Configuration with Custom Stamps and Timestamps
|
|
152
|
+
|
|
153
|
+
A production-ready implementation with configuration file, custom stamps, timestamps, and advanced features:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { useState, useEffect } from 'react';
|
|
157
|
+
import { PdfViewer } from 'hazo_pdf';
|
|
158
|
+
import type { PdfAnnotation, PDFDocumentProxy } from 'hazo_pdf';
|
|
159
|
+
import 'hazo_pdf/styles.css';
|
|
160
|
+
|
|
161
|
+
function ProductionViewer() {
|
|
162
|
+
const [annotations, setAnnotations] = useState<PdfAnnotation[]>([]);
|
|
163
|
+
const [initialScale, setInitialScale] = useState(1.0);
|
|
164
|
+
|
|
165
|
+
// Load saved annotations from localStorage
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const saved = localStorage.getItem('pdf-annotations');
|
|
168
|
+
if (saved) {
|
|
169
|
+
try {
|
|
170
|
+
setAnnotations(JSON.parse(saved));
|
|
171
|
+
} catch (e) {
|
|
172
|
+
console.error('Failed to load saved annotations:', e);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Save window size to adjust scale
|
|
177
|
+
const updateScale = () => {
|
|
178
|
+
const width = window.innerWidth;
|
|
179
|
+
if (width < 768) {
|
|
180
|
+
setInitialScale(0.75); // Mobile
|
|
181
|
+
} else if (width < 1024) {
|
|
182
|
+
setInitialScale(1.0); // Tablet
|
|
183
|
+
} else {
|
|
184
|
+
setInitialScale(1.25); // Desktop
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
updateScale();
|
|
189
|
+
window.addEventListener('resize', updateScale);
|
|
190
|
+
return () => window.removeEventListener('resize', updateScale);
|
|
191
|
+
}, []);
|
|
192
|
+
|
|
193
|
+
// Persist annotations to localStorage
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
if (annotations.length > 0) {
|
|
196
|
+
localStorage.setItem('pdf-annotations', JSON.stringify(annotations));
|
|
197
|
+
}
|
|
198
|
+
}, [annotations]);
|
|
199
|
+
|
|
200
|
+
const handleAnnotationCreate = (annotation: PdfAnnotation) => {
|
|
201
|
+
setAnnotations(prev => {
|
|
202
|
+
const updated = [...prev, annotation];
|
|
203
|
+
// Save to server
|
|
204
|
+
fetch('/api/annotations', {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers: { 'Content-Type': 'application/json' },
|
|
207
|
+
body: JSON.stringify(annotation),
|
|
208
|
+
}).catch(err => console.error('Failed to save annotation:', err));
|
|
209
|
+
return updated;
|
|
210
|
+
});
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const handleAnnotationUpdate = (annotation: PdfAnnotation) => {
|
|
214
|
+
setAnnotations(prev => {
|
|
215
|
+
const updated = prev.map(a => a.id === annotation.id ? annotation : a);
|
|
216
|
+
// Update on server
|
|
217
|
+
fetch(`/api/annotations/${annotation.id}`, {
|
|
218
|
+
method: 'PUT',
|
|
219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
220
|
+
body: JSON.stringify(annotation),
|
|
221
|
+
}).catch(err => console.error('Failed to update annotation:', err));
|
|
222
|
+
return updated;
|
|
223
|
+
});
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const handleAnnotationDelete = (annotationId: string) => {
|
|
227
|
+
setAnnotations(prev => {
|
|
228
|
+
const updated = prev.filter(a => a.id !== annotationId);
|
|
229
|
+
// Delete on server
|
|
230
|
+
fetch(`/api/annotations/${annotationId}`, {
|
|
231
|
+
method: 'DELETE',
|
|
232
|
+
}).catch(err => console.error('Failed to delete annotation:', err));
|
|
233
|
+
return updated;
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const handleSave = async (pdfBytes: Uint8Array, filename: string) => {
|
|
238
|
+
try {
|
|
239
|
+
// Upload to server
|
|
240
|
+
const formData = new FormData();
|
|
241
|
+
formData.append('file', new Blob([pdfBytes], { type: 'application/pdf' }), filename);
|
|
242
|
+
|
|
243
|
+
const response = await fetch('/api/documents/upload', {
|
|
244
|
+
method: 'POST',
|
|
245
|
+
body: formData,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
if (response.ok) {
|
|
249
|
+
const result = await response.json();
|
|
250
|
+
alert(`PDF saved successfully: ${result.url}`);
|
|
251
|
+
} else {
|
|
252
|
+
throw new Error('Failed to upload PDF');
|
|
253
|
+
}
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error('Save error:', error);
|
|
256
|
+
alert('Failed to save PDF');
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Custom stamps configuration
|
|
261
|
+
const customStamps = JSON.stringify([
|
|
262
|
+
{
|
|
263
|
+
name: "Verified",
|
|
264
|
+
text: "✅",
|
|
265
|
+
order: 1,
|
|
266
|
+
time_stamp_suffix_enabled: true,
|
|
267
|
+
fixed_text_suffix_enabled: true,
|
|
268
|
+
background_color: "rgb(255, 255, 255)",
|
|
269
|
+
border_size: 0,
|
|
270
|
+
font_color: "#000000",
|
|
271
|
+
font_weight: "bold",
|
|
272
|
+
font_size: 16
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: "Rejected",
|
|
276
|
+
text: "❌",
|
|
277
|
+
order: 2,
|
|
278
|
+
time_stamp_suffix_enabled: true,
|
|
279
|
+
fixed_text_suffix_enabled: false,
|
|
280
|
+
background_color: "rgb(255, 200, 200)",
|
|
281
|
+
border_size: 1,
|
|
282
|
+
font_color: "#000000",
|
|
283
|
+
font_size: 14
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "Needs Review",
|
|
287
|
+
text: "⚠️",
|
|
288
|
+
order: 3,
|
|
289
|
+
time_stamp_suffix_enabled: false,
|
|
290
|
+
fixed_text_suffix_enabled: false,
|
|
291
|
+
background_color: "rgb(255, 255, 200)",
|
|
292
|
+
border_size: 2,
|
|
293
|
+
font_color: "#000000",
|
|
294
|
+
font_size: 12
|
|
295
|
+
}
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
return (
|
|
299
|
+
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
300
|
+
<div style={{ padding: '1rem', background: '#f0f0f0', borderBottom: '1px solid #ccc' }}>
|
|
301
|
+
<h1>Document Viewer</h1>
|
|
302
|
+
<p>Annotations: {annotations.length} |
|
|
303
|
+
<button onClick={() => setAnnotations([])}>Clear All</button>
|
|
304
|
+
</p>
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<div style={{ flex: 1, overflow: 'hidden' }}>
|
|
308
|
+
<PdfViewer
|
|
309
|
+
url="/api/documents/contract.pdf"
|
|
310
|
+
config_file="hazo_pdf_config.ini"
|
|
311
|
+
className="h-full w-full"
|
|
312
|
+
scale={initialScale}
|
|
313
|
+
annotations={annotations}
|
|
314
|
+
on_annotation_create={handleAnnotationCreate}
|
|
315
|
+
on_annotation_update={handleAnnotationUpdate}
|
|
316
|
+
on_annotation_delete={handleAnnotationDelete}
|
|
317
|
+
on_save={handleSave}
|
|
318
|
+
append_timestamp_to_text_edits={true}
|
|
319
|
+
annotation_text_suffix_fixed_text="Reviewer"
|
|
320
|
+
right_click_custom_stamps={customStamps}
|
|
321
|
+
background_color="#ffffff"
|
|
322
|
+
/>
|
|
323
|
+
</div>
|
|
324
|
+
</div>
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Features demonstrated:**
|
|
330
|
+
- Configuration file integration (`hazo_pdf_config.ini`)
|
|
331
|
+
- Custom stamps with styling
|
|
332
|
+
- Timestamp and fixed text suffixes
|
|
333
|
+
- Responsive scaling based on screen size
|
|
334
|
+
- LocalStorage persistence
|
|
335
|
+
- Server synchronization
|
|
336
|
+
- Complex state management
|
|
337
|
+
- Custom UI wrapper
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Configuration File
|
|
342
|
+
|
|
343
|
+
The PDF viewer can be configured via an INI file (default: `hazo_pdf_config.ini`). This allows you to customize styling, colors, fonts, and behavior without modifying code.
|
|
344
|
+
|
|
345
|
+
**Basic setup:**
|
|
346
|
+
|
|
347
|
+
```ini
|
|
348
|
+
[viewer]
|
|
349
|
+
viewer_background_color = #f5f5f5
|
|
350
|
+
append_timestamp_to_text_edits = true
|
|
351
|
+
annotation_text_suffix_fixed_text = user_x
|
|
352
|
+
|
|
353
|
+
[freetext_annotation]
|
|
354
|
+
freetext_text_color = #0066cc
|
|
355
|
+
freetext_background_color = rgb(230, 243, 255)
|
|
356
|
+
freetext_background_opacity = 0.1
|
|
357
|
+
freetext_border_color = #003366
|
|
358
|
+
freetext_border_width = 1
|
|
359
|
+
|
|
360
|
+
[context_menu]
|
|
361
|
+
right_click_custom_stamps = [{"name":"Verified","text":"✅","order":1,"time_stamp_suffix_enabled":true,"fixed_text_suffix_enabled":true}]
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
See `hazo_pdf_config.ini` in the project root for all available configuration options.
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## API Reference
|
|
369
|
+
|
|
370
|
+
### PdfViewer Props
|
|
371
|
+
|
|
372
|
+
#### Required Props
|
|
373
|
+
|
|
374
|
+
| Prop | Type | Description |
|
|
375
|
+
|------|------|-------------|
|
|
376
|
+
| `url` | `string` | URL or path to the PDF file. Can be a relative path, absolute URL, or API endpoint. |
|
|
377
|
+
|
|
378
|
+
#### Optional Props
|
|
379
|
+
|
|
380
|
+
##### Basic Configuration
|
|
381
|
+
|
|
382
|
+
| Prop | Type | Default | Description |
|
|
383
|
+
|------|------|---------|-------------|
|
|
384
|
+
| `className` | `string` | `""` | Additional CSS classes to apply to the viewer container. |
|
|
385
|
+
| `scale` | `number` | `1.0` | Initial zoom level. Values > 1.0 zoom in, < 1.0 zoom out. |
|
|
386
|
+
| `background_color` | `string` | `"#2d2d2d"` | Background color for areas outside PDF pages (hex format: `#RRGGBB`). Overrides config file value. |
|
|
387
|
+
| `config_file` | `string` | `undefined` | Path to configuration INI file (e.g., `"hazo_pdf_config.ini"`). If not provided, uses default configuration. |
|
|
388
|
+
|
|
389
|
+
##### Event Callbacks
|
|
390
|
+
|
|
391
|
+
| Prop | Type | Description |
|
|
392
|
+
|------|------|-------------|
|
|
393
|
+
| `on_load` | `(pdf: PDFDocumentProxy) => void` | Called when PDF is successfully loaded. Receives the PDF document proxy with metadata (page count, etc.). |
|
|
394
|
+
| `on_error` | `(error: Error) => void` | Called when an error occurs (PDF load failure, rendering error, etc.). Receives the error object. |
|
|
395
|
+
| `on_save` | `(pdf_bytes: Uint8Array, filename: string) => void` | Called when user clicks the Save button. Receives the PDF bytes with annotations embedded and a suggested filename. You can create a Blob and trigger download, or upload to a server. |
|
|
396
|
+
|
|
397
|
+
##### Annotation Management
|
|
398
|
+
|
|
399
|
+
| Prop | Type | Default | Description |
|
|
400
|
+
|------|------|---------|-------------|
|
|
401
|
+
| `annotations` | `PdfAnnotation[]` | `[]` | Array of existing annotations to display. Used to restore saved annotations or sync from a server. |
|
|
402
|
+
| `on_annotation_create` | `(annotation: PdfAnnotation) => void` | `undefined` | Called when a new annotation is created (Square or FreeText). Use this to persist annotations to your backend. |
|
|
403
|
+
| `on_annotation_update` | `(annotation: PdfAnnotation) => void` | `undefined` | Called when an existing annotation is edited (text content changed). Use this to sync updates to your backend. |
|
|
404
|
+
| `on_annotation_delete` | `(annotation_id: string) => void` | `undefined` | Called when an annotation is deleted. Receives the annotation ID. Use this to remove annotations from your backend. |
|
|
405
|
+
|
|
406
|
+
##### Timestamp and Suffix Configuration
|
|
407
|
+
|
|
408
|
+
| Prop | Type | Default | Description |
|
|
409
|
+
|------|------|---------|-------------|
|
|
410
|
+
| `append_timestamp_to_text_edits` | `boolean` | `false` | If `true`, automatically appends a timestamp to all FreeText annotations when created or edited. Format: `[YYYY-MM-DD h:mmam/pm]`. Overrides config file value. |
|
|
411
|
+
| `annotation_text_suffix_fixed_text` | `string` | `""` | Fixed text string to append before the timestamp (if timestamps are enabled). This text will be enclosed in brackets. Overrides config file value. |
|
|
412
|
+
|
|
413
|
+
##### Custom Stamps
|
|
414
|
+
|
|
415
|
+
| Prop | Type | Default | Description |
|
|
416
|
+
|------|------|---------|-------------|
|
|
417
|
+
| `right_click_custom_stamps` | `string` | `undefined` | JSON array string defining custom stamp menu items for the right-click context menu. Each stamp appears as a menu item that, when clicked, adds predefined text to the PDF. Overrides config file value. |
|
|
418
|
+
|
|
419
|
+
**Custom Stamp JSON Format:**
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
[
|
|
423
|
+
{
|
|
424
|
+
name: string; // Menu item label (required)
|
|
425
|
+
text: string; // Text to add to PDF (required)
|
|
426
|
+
order: number; // Menu position, lower = higher (required)
|
|
427
|
+
time_stamp_suffix_enabled?: boolean; // Append timestamp? (default: false)
|
|
428
|
+
fixed_text_suffix_enabled?: boolean; // Append fixed text? (default: false)
|
|
429
|
+
background_color?: string; // Hex or rgb() format (default: config)
|
|
430
|
+
border_size?: number; // Pixels, 0 = no border (default: config)
|
|
431
|
+
font_color?: string; // Hex or rgb() format (default: config)
|
|
432
|
+
font_weight?: string; // CSS font-weight (default: config)
|
|
433
|
+
font_style?: string; // CSS font-style (default: config)
|
|
434
|
+
font_size?: number; // Pixels (default: config)
|
|
435
|
+
font_name?: string; // CSS font-family (default: config)
|
|
436
|
+
}
|
|
437
|
+
]
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**Example:**
|
|
441
|
+
|
|
442
|
+
```tsx
|
|
443
|
+
const stamps = JSON.stringify([
|
|
444
|
+
{
|
|
445
|
+
name: "Approved",
|
|
446
|
+
text: "✓",
|
|
447
|
+
order: 1,
|
|
448
|
+
time_stamp_suffix_enabled: true,
|
|
449
|
+
background_color: "rgb(200, 255, 200)",
|
|
450
|
+
border_size: 1,
|
|
451
|
+
font_color: "#000000",
|
|
452
|
+
font_weight: "bold",
|
|
453
|
+
font_size: 16
|
|
454
|
+
}
|
|
455
|
+
]);
|
|
456
|
+
|
|
457
|
+
<PdfViewer
|
|
458
|
+
url="/document.pdf"
|
|
459
|
+
right_click_custom_stamps={stamps}
|
|
460
|
+
/>
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### PdfAnnotation Interface
|
|
464
|
+
|
|
465
|
+
Represents a PDF annotation in the standard PDF coordinate space.
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
interface PdfAnnotation {
|
|
469
|
+
id: string; // Unique identifier (auto-generated)
|
|
470
|
+
type: 'Square' | 'FreeText' | 'Highlight' | 'CustomBookmark';
|
|
471
|
+
page_index: number; // Zero-based page number
|
|
472
|
+
rect: [number, number, number, number]; // PDF coordinates [x1, y1, x2, y2]
|
|
473
|
+
author: string; // Author name (default: "User")
|
|
474
|
+
date: string; // ISO date string
|
|
475
|
+
contents: string; // Annotation text/content
|
|
476
|
+
color?: string; // Color in hex format (e.g., "#FF0000")
|
|
477
|
+
subject?: string; // Optional subject/title
|
|
478
|
+
flags?: string; // Optional PDF flags
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### PDFDocumentProxy
|
|
483
|
+
|
|
484
|
+
Type from `pdfjs-dist`. Contains PDF metadata and page proxies.
|
|
485
|
+
|
|
486
|
+
**Common properties:**
|
|
487
|
+
- `numPages: number` - Total number of pages
|
|
488
|
+
- `getPage(pageNumber: number): Promise<PDFPageProxy>` - Get a specific page
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## Usage Tips
|
|
493
|
+
|
|
494
|
+
### Styling
|
|
495
|
+
|
|
496
|
+
- The viewer uses TailwindCSS classes. Ensure TailwindCSS is configured in your project or import the bundled styles.
|
|
497
|
+
- Use the `className` prop to add custom wrapper styles.
|
|
498
|
+
- Use the configuration file for comprehensive styling customization.
|
|
499
|
+
|
|
500
|
+
### Annotation Coordinate System
|
|
501
|
+
|
|
502
|
+
- Annotations use PDF coordinate space (points), not screen pixels.
|
|
503
|
+
- PDF coordinates start at the bottom-left (Y increases upward).
|
|
504
|
+
- The component handles coordinate conversion automatically.
|
|
505
|
+
|
|
506
|
+
### Pan Tool
|
|
507
|
+
|
|
508
|
+
- Pan is the default tool (`current_tool = null`).
|
|
509
|
+
- Users can drag to scroll/pan the document.
|
|
510
|
+
- The cursor changes to a hand icon when panning.
|
|
511
|
+
|
|
512
|
+
### Square Annotations
|
|
513
|
+
|
|
514
|
+
1. Click the "Square" button in the toolbar.
|
|
515
|
+
2. Click and drag on the PDF to create a rectangle.
|
|
516
|
+
3. Right-click the rectangle to add a comment.
|
|
517
|
+
|
|
518
|
+
### FreeText Annotations
|
|
519
|
+
|
|
520
|
+
1. Right-click anywhere on the PDF.
|
|
521
|
+
2. Select "Annotate" from the context menu.
|
|
522
|
+
3. Enter text in the dialog.
|
|
523
|
+
4. Click the checkmark to save.
|
|
524
|
+
5. Left-click an existing FreeText annotation to edit or delete it.
|
|
525
|
+
|
|
526
|
+
### Custom Stamps
|
|
527
|
+
|
|
528
|
+
1. Configure stamps via `right_click_custom_stamps` prop or config file.
|
|
529
|
+
2. Right-click on the PDF.
|
|
530
|
+
3. Select a stamp from the bottom of the menu.
|
|
531
|
+
4. The stamp text is added at the click position with optional timestamp/fixed text.
|
|
532
|
+
|
|
533
|
+
### Saving Annotations
|
|
534
|
+
|
|
535
|
+
- Click the "Save" button in the toolbar.
|
|
536
|
+
- The `on_save` callback receives PDF bytes with annotations embedded using `pdf-lib`.
|
|
537
|
+
- You can download directly or upload to a server.
|
|
538
|
+
|
|
539
|
+
**Example download:**
|
|
540
|
+
|
|
541
|
+
```tsx
|
|
542
|
+
const handleSave = (pdfBytes: Uint8Array, filename: string) => {
|
|
543
|
+
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
|
544
|
+
const url = URL.createObjectURL(blob);
|
|
545
|
+
const a = document.createElement('a');
|
|
546
|
+
a.href = url;
|
|
547
|
+
a.download = filename || 'document.pdf';
|
|
548
|
+
a.click();
|
|
549
|
+
URL.revokeObjectURL(url);
|
|
550
|
+
};
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
### Timestamp Formatting
|
|
554
|
+
|
|
555
|
+
When `append_timestamp_to_text_edits` is enabled, timestamps are formatted as:
|
|
556
|
+
- Format: `YYYY-MM-DD h:mmam/pm`
|
|
557
|
+
- Example: `2025-11-17 2:24pm`
|
|
558
|
+
|
|
559
|
+
The position and bracket style can be configured via the config file:
|
|
560
|
+
- `suffix_text_position`: `"adjacent"`, `"below_single_line"`, or `"below_multi_line"` (default)
|
|
561
|
+
- `suffix_enclosing_brackets`: Two-character string like `"[]"`, `"()"`, `"{}"` (default: `"[]"`)
|
|
562
|
+
- `add_enclosing_brackets_to_suffixes`: `true` or `false` (default: `true`)
|
|
563
|
+
|
|
564
|
+
---
|
|
565
|
+
|
|
33
566
|
## Development
|
|
34
567
|
|
|
35
568
|
### Setup
|
|
@@ -44,13 +577,26 @@ npm install
|
|
|
44
577
|
npm run build
|
|
45
578
|
```
|
|
46
579
|
|
|
580
|
+
This builds the library to `dist/` using `tsup`.
|
|
581
|
+
|
|
47
582
|
### Watch Mode
|
|
48
583
|
|
|
49
584
|
```bash
|
|
50
585
|
npm run dev
|
|
51
586
|
```
|
|
52
587
|
|
|
588
|
+
This runs `tsup` in watch mode, rebuilding on file changes.
|
|
589
|
+
|
|
590
|
+
### Test App
|
|
591
|
+
|
|
592
|
+
```bash
|
|
593
|
+
npm run test-app:dev
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
Starts the Next.js test application (if `test_app_enabled = true` in config).
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
53
600
|
## License
|
|
54
601
|
|
|
55
602
|
MIT © Pubs Abayasiri
|
|
56
|
-
|