react-pdf-highlighter-plus 1.1.3 → 1.2.0
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 +235 -52
- package/dist/esm/export-pdf-W2QGWADM.js +403 -0
- package/dist/esm/export-pdf-W2QGWADM.js.map +1 -0
- package/dist/esm/index.d.ts +309 -86
- package/dist/esm/index.js +2417 -820
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/pdf.worker.min.mjs +21 -0
- package/dist/esm/style/AreaHighlight.css +9 -2
- package/dist/esm/style/DrawingHighlight.css +10 -0
- package/dist/esm/style/FreetextHighlight.css +60 -0
- package/dist/esm/style/ImageHighlight.css +2 -1
- package/dist/esm/style/PdfHighlighter.css +103 -12
- package/dist/esm/style/TextHighlight.css +9 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
</p>
|
|
21
21
|
|
|
22
22
|
<p align="center">
|
|
23
|
-
Text highlights • Area highlights • Freetext notes • Images & signatures • Freehand drawing • PDF export
|
|
23
|
+
Text highlights • Area highlights • Freetext notes • Images & signatures • Freehand drawing • Shapes • Search • PDF export
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
26
|
---
|
|
@@ -29,18 +29,30 @@
|
|
|
29
29
|
|
|
30
30
|
`react-pdf-highlighter-plus` provides a highly customizable annotation experience for PDF documents in React applications. Built on [PDF.js](https://github.com/mozilla/pdf.js), it stores highlight positions in viewport-independent coordinates, making them portable across different screen sizes.
|
|
31
31
|
|
|
32
|
+
<p align="center">
|
|
33
|
+
<img src="docs/react-pdf-highlight-plus.png" alt="react-pdf-highlighter-plus internal architecture" width="900">
|
|
34
|
+
</p>
|
|
35
|
+
|
|
32
36
|
## Features
|
|
33
37
|
|
|
34
38
|
| Feature | Description |
|
|
35
39
|
|---------|-------------|
|
|
36
|
-
| **Text Highlights** | Select and highlight text passages |
|
|
37
|
-
| **Area Highlights** | Draw rectangular regions on PDFs |
|
|
38
|
-
| **Freetext Notes** | Draggable, editable sticky notes with custom styling |
|
|
40
|
+
| **Text Highlights** | Select and highlight text passages, restyle them, and copy their text |
|
|
41
|
+
| **Area Highlights** | Draw rectangular regions on PDFs and copy intersecting PDF text |
|
|
42
|
+
| **Freetext Notes** | Draggable, editable sticky notes with custom styling and compact mode |
|
|
39
43
|
| **Images & Signatures** | Upload images or draw signatures directly on PDFs |
|
|
40
44
|
| **Freehand Drawing** | Draw freehand annotations with customizable stroke |
|
|
45
|
+
| **Shapes** | Add rectangles, circles, and arrows with editable stroke style |
|
|
46
|
+
| **PDF Search** | Search through all PDF text with next/previous navigation |
|
|
41
47
|
| **PDF Export** | Export annotated PDF with all highlights embedded |
|
|
42
|
-
| **
|
|
43
|
-
| **
|
|
48
|
+
| **Local PDF Worker** | Uses the packaged PDF.js worker by default |
|
|
49
|
+
| **Light/Dark Theme** | Hue-preserving dark mode (OKLab recolor) — photos & colors stay readable |
|
|
50
|
+
| **Zoom Support** | Buttons, **pinch / ctrl+wheel zoom**, position-independent data |
|
|
51
|
+
| **Smooth Scroll** | Animated scroll-to-highlight (respects reduced-motion) |
|
|
52
|
+
| **Deep Linking** | `initialPage` + `onPageChange` for `?page=N` style navigation |
|
|
53
|
+
| **Locate Text** | `getTextPosition` — turn any quote into a precise highlight (citations) |
|
|
54
|
+
| **Read Aloud** | Text-to-speech that highlights & follows each sentence (recipe) |
|
|
55
|
+
| **Fast Loading** | Progressive range loading, auth headers, document caching |
|
|
44
56
|
| **Fully Customizable** | Exposed styling on all components |
|
|
45
57
|
|
|
46
58
|
## Quick Links
|
|
@@ -65,6 +77,8 @@ npm install react-pdf-highlighter-plus
|
|
|
65
77
|
import "react-pdf-highlighter-plus/style/style.css";
|
|
66
78
|
```
|
|
67
79
|
|
|
80
|
+
PDF.js worker setup is handled by the package by default. The build copies the local `pdf.worker.min.mjs` into the package output, so most apps do not need to configure `workerSrc` manually.
|
|
81
|
+
|
|
68
82
|
---
|
|
69
83
|
|
|
70
84
|
## Quick Start
|
|
@@ -126,6 +140,10 @@ Select text in the PDF to create highlights.
|
|
|
126
140
|
/>
|
|
127
141
|
```
|
|
128
142
|
|
|
143
|
+
Text and area highlight toolbars include a copy button. After copying, the icon changes to a check mark for 1.5 seconds.
|
|
144
|
+
|
|
145
|
+
[Full Documentation →](docs/text-area-highlights.md)
|
|
146
|
+
|
|
129
147
|
### 2. Area Highlights
|
|
130
148
|
|
|
131
149
|
Hold `Alt` and drag to create rectangular highlights.
|
|
@@ -211,14 +229,12 @@ Draw freehand annotations directly on PDFs.
|
|
|
211
229
|
import { DrawingHighlight } from "react-pdf-highlighter-plus";
|
|
212
230
|
|
|
213
231
|
<PdfHighlighter
|
|
214
|
-
|
|
215
|
-
onDrawingComplete={(position,
|
|
232
|
+
enableDrawingMode={drawingMode}
|
|
233
|
+
onDrawingComplete={(dataUrl, position, strokes) => {
|
|
216
234
|
addHighlight({ type: "drawing", position, content: { image: dataUrl } });
|
|
217
235
|
}}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
strokeWidth: 2,
|
|
221
|
-
}}
|
|
236
|
+
drawingStrokeColor="#ff0000"
|
|
237
|
+
drawingStrokeWidth={2}
|
|
222
238
|
>
|
|
223
239
|
|
|
224
240
|
// In your highlight container:
|
|
@@ -238,12 +254,63 @@ import { DrawingHighlight } from "react-pdf-highlighter-plus";
|
|
|
238
254
|
|
|
239
255
|
---
|
|
240
256
|
|
|
257
|
+
## Shapes
|
|
258
|
+
|
|
259
|
+
Create rectangle, circle, and arrow annotations with editable stroke color and width.
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
<PdfHighlighter
|
|
263
|
+
enableShapeMode={shapeMode} // "rectangle" | "circle" | "arrow" | null
|
|
264
|
+
onShapeComplete={(position, shape) => {
|
|
265
|
+
addHighlight({ type: "shape", position, content: { shape } });
|
|
266
|
+
}}
|
|
267
|
+
shapeStrokeColor="#000000"
|
|
268
|
+
shapeStrokeWidth={2}
|
|
269
|
+
>
|
|
270
|
+
<HighlightContainer />
|
|
271
|
+
</PdfHighlighter>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Shape geometry stays in the normal highlight layer while the style controls render in the higher config layer.
|
|
275
|
+
|
|
276
|
+
[Full Documentation →](docs/shape-highlights.md)
|
|
277
|
+
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
## PDF Search
|
|
281
|
+
|
|
282
|
+
Use `utilsRef` to access document-wide search helpers backed by PDF.js `PDFFindController`.
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
const highlighterUtilsRef = useRef<PdfHighlighterUtils>();
|
|
286
|
+
|
|
287
|
+
<PdfHighlighter
|
|
288
|
+
pdfDocument={pdfDocument}
|
|
289
|
+
highlights={highlights}
|
|
290
|
+
utilsRef={(utils) => (highlighterUtilsRef.current = utils)}
|
|
291
|
+
>
|
|
292
|
+
<HighlightContainer />
|
|
293
|
+
</PdfHighlighter>
|
|
294
|
+
|
|
295
|
+
highlighterUtilsRef.current?.search("TypeScript", {
|
|
296
|
+
highlightAll: true,
|
|
297
|
+
caseSensitive: false,
|
|
298
|
+
});
|
|
299
|
+
highlighterUtilsRef.current?.findNext();
|
|
300
|
+
highlighterUtilsRef.current?.clearSearch();
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
241
305
|
## Light/Dark Theme
|
|
242
306
|
|
|
243
|
-
|
|
307
|
+
Dark mode recolors each page **at render time** using a hue-preserving OKLab map —
|
|
308
|
+
white paper maps to a dark background and black text to a light foreground, while
|
|
309
|
+
**colors keep their hue and embedded photos keep their pixels** (unlike a CSS
|
|
310
|
+
`invert()` filter). Highlights, the text selection, and the left panel all adapt.
|
|
244
311
|
|
|
245
312
|
```tsx
|
|
246
|
-
// Enable dark mode
|
|
313
|
+
// Enable dark mode (warm-gray default palette)
|
|
247
314
|
<PdfHighlighter
|
|
248
315
|
pdfDocument={pdfDocument}
|
|
249
316
|
theme={{ mode: "dark" }}
|
|
@@ -252,12 +319,15 @@ Toggle between light and dark modes with customizable styling for comfortable re
|
|
|
252
319
|
<HighlightContainer />
|
|
253
320
|
</PdfHighlighter>
|
|
254
321
|
|
|
255
|
-
// Customize dark
|
|
322
|
+
// Customize the dark palette
|
|
256
323
|
<PdfHighlighter
|
|
257
324
|
pdfDocument={pdfDocument}
|
|
258
325
|
theme={{
|
|
259
326
|
mode: "dark",
|
|
260
|
-
|
|
327
|
+
darkModeColors: {
|
|
328
|
+
background: "#141210", // replaces white paper
|
|
329
|
+
foreground: "#eae6e0", // replaces black text / line-art
|
|
330
|
+
},
|
|
261
331
|
containerBackgroundColor: "#3a3a3a",
|
|
262
332
|
scrollbarThumbColor: "#6b6b6b",
|
|
263
333
|
scrollbarTrackColor: "#2c2c2c",
|
|
@@ -268,25 +338,146 @@ Toggle between light and dark modes with customizable styling for comfortable re
|
|
|
268
338
|
</PdfHighlighter>
|
|
269
339
|
```
|
|
270
340
|
|
|
271
|
-
**
|
|
272
|
-
-
|
|
273
|
-
-
|
|
274
|
-
-
|
|
275
|
-
-
|
|
276
|
-
-
|
|
277
|
-
|
|
278
|
-
**
|
|
279
|
-
|
|
280
|
-
|-------|--------|----------|
|
|
281
|
-
| `1.0` | Pure black | High contrast |
|
|
282
|
-
| `0.9` | Dark gray (~#1a1a1a) | **Recommended** |
|
|
283
|
-
| `0.85` | Softer gray (~#262626) | Long reading sessions |
|
|
284
|
-
| `0.8` | Medium gray (~#333333) | Maximum comfort |
|
|
341
|
+
**Highlights:**
|
|
342
|
+
- Hue-preserving recolor (red stays red, blue links stay blue); photos untouched.
|
|
343
|
+
- Highlights stay readable: translucent fill + a border, with no `mix-blend` wash-out.
|
|
344
|
+
- Scroll **and** zoom are preserved when toggling the theme.
|
|
345
|
+
- `LeftPanel` accepts `mode="dark"` so the outline/thumbnails panel matches.
|
|
346
|
+
- Drawing/shape default ink becomes white in dark mode.
|
|
347
|
+
|
|
348
|
+
> **Deprecated:** `theme.darkModeInvertIntensity` is ignored — dark mode no longer
|
|
349
|
+
> uses a CSS `invert()` filter. Use `theme.darkModeColors` instead.
|
|
285
350
|
|
|
286
351
|
[Full Documentation →](docs/theming.md)
|
|
287
352
|
|
|
288
353
|
---
|
|
289
354
|
|
|
355
|
+
## Loading & Performance
|
|
356
|
+
|
|
357
|
+
`PdfLoader` loads documents progressively and caches them.
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
<PdfLoader
|
|
361
|
+
document="https://api.example.com/files/report.pdf"
|
|
362
|
+
// Fetch only the pages needed to render first (needs server HTTP range support)
|
|
363
|
+
disableAutoFetch={true}
|
|
364
|
+
// Auth / cross-origin
|
|
365
|
+
httpHeaders={{ Authorization: `Bearer ${token}` }}
|
|
366
|
+
withCredentials={false}
|
|
367
|
+
// Reuse the same URL instantly on remount / re-open (default true)
|
|
368
|
+
enableCache={true}
|
|
369
|
+
>
|
|
370
|
+
{(pdfDocument) => <PdfHighlighter pdfDocument={pdfDocument} /* … */ />}
|
|
371
|
+
</PdfLoader>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
| Prop | Default | Description |
|
|
375
|
+
|------|---------|-------------|
|
|
376
|
+
| `disableAutoFetch` | `true` | Fetch pages on demand instead of the whole file (first page shows fast on range-capable servers) |
|
|
377
|
+
| `disableStream` | `false` | Disable progressive streaming |
|
|
378
|
+
| `rangeChunkSize` | pdf.js | Size of each range request in bytes |
|
|
379
|
+
| `httpHeaders` | – | Extra request headers (e.g. an auth token) |
|
|
380
|
+
| `withCredentials` | `false` | Send cookies with the request |
|
|
381
|
+
| `enableCache` | `true` | Cache the loaded document by URL (also dedupes StrictMode double-mount) |
|
|
382
|
+
| `beforeLoad` | spinner | Render while loading; receives progress or `null` |
|
|
383
|
+
|
|
384
|
+
> Progressive loading requires the server to support HTTP range requests
|
|
385
|
+
> (`Accept-Ranges: bytes`) **and** expose `Content-Range` via CORS.
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
389
|
+
## Navigation, Zoom & Smooth Scroll
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
<PdfHighlighter
|
|
393
|
+
pdfDocument={pdfDocument}
|
|
394
|
+
initialPage={12} // jump here on first load (deep-link)
|
|
395
|
+
onPageChange={(page) => syncUrl(page)} // current page changed
|
|
396
|
+
onZoomChange={(scale) => setZoom(scale)} // pinch / ctrl+wheel zoom changed
|
|
397
|
+
highlights={highlights}
|
|
398
|
+
>
|
|
399
|
+
<HighlightContainer />
|
|
400
|
+
</PdfHighlighter>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
- **Pinch / ctrl(⌘)+wheel zoom** is built in — smooth (GPU transform during the
|
|
404
|
+
gesture, one crisp re-render on settle), anchored to the cursor.
|
|
405
|
+
- **`scrollToHighlight(highlight)`** (from `usePdfHighlighterContext`) now scrolls
|
|
406
|
+
**smoothly** and respects `prefers-reduced-motion`.
|
|
407
|
+
- `initialPage` is applied once on load; `onPageChange` fires as the visible page
|
|
408
|
+
changes — wire them to a `?page=N` URL for deep links.
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## Locating Text (Citations)
|
|
413
|
+
|
|
414
|
+
`getTextPosition` finds a piece of text in the PDF and returns a precise
|
|
415
|
+
`ScaledPosition` — turn an external quote (e.g. an AI citation) into a highlight
|
|
416
|
+
you can render or scroll to. Matching ignores whitespace/line-wraps and falls
|
|
417
|
+
back to fuzzy matching.
|
|
418
|
+
|
|
419
|
+
```tsx
|
|
420
|
+
import { getTextPosition } from "react-pdf-highlighter-plus";
|
|
421
|
+
|
|
422
|
+
const match = await getTextPosition(pdfDocument, "the exact or near-exact quote");
|
|
423
|
+
if (match) {
|
|
424
|
+
const citation = {
|
|
425
|
+
id: "cite-1",
|
|
426
|
+
type: "text",
|
|
427
|
+
content: { text: match.matchedText },
|
|
428
|
+
position: match.position, // precise rects, page-independent
|
|
429
|
+
};
|
|
430
|
+
setHighlights((prev) => [citation, ...prev]);
|
|
431
|
+
utils.scrollToHighlight(citation); // smooth scroll + flash
|
|
432
|
+
}
|
|
433
|
+
// match: { position, pageNumber, matchedText, confidence: "exact" | "fuzzy" }
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Read Aloud (Text-to-Speech)
|
|
439
|
+
|
|
440
|
+
Build a read-aloud / "PDF to audio" experience: speak the document and
|
|
441
|
+
highlight + auto-scroll to each sentence as it's read. The pieces are already
|
|
442
|
+
here — `extractSentences` gives the ordered script (text + position) and
|
|
443
|
+
`scrollToHighlight` follows along. The TTS engine is yours to choose (the
|
|
444
|
+
browser `speechSynthesis`, or a cloud voice).
|
|
445
|
+
|
|
446
|
+
```tsx
|
|
447
|
+
import { extractSentences } from "react-pdf-highlighter-plus";
|
|
448
|
+
|
|
449
|
+
// 1. Build the ordered script once.
|
|
450
|
+
const sentences = (
|
|
451
|
+
await extractSentences(pdfDocument, { includePositions: true })
|
|
452
|
+
).filter((s) => s.position);
|
|
453
|
+
|
|
454
|
+
// 2. Speak each sentence; highlight + scroll as it starts.
|
|
455
|
+
function readFrom(index: number) {
|
|
456
|
+
const s = sentences[index];
|
|
457
|
+
if (!s) return;
|
|
458
|
+
|
|
459
|
+
const reading = {
|
|
460
|
+
id: "reading",
|
|
461
|
+
type: "text",
|
|
462
|
+
content: { text: s.text },
|
|
463
|
+
position: s.position!,
|
|
464
|
+
};
|
|
465
|
+
setHighlights((prev) => [reading, ...prev.filter((h) => h.id !== "reading")]);
|
|
466
|
+
utils.scrollToHighlight(reading); // smooth follow
|
|
467
|
+
|
|
468
|
+
const utter = new SpeechSynthesisUtterance(s.text);
|
|
469
|
+
utter.onend = () => readFrom(index + 1); // advance
|
|
470
|
+
speechSynthesis.speak(utter);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
readFrom(0);
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Swap `speechSynthesis` for any engine — sentence-level sync needs no word
|
|
477
|
+
timing. See the example app for a full transport (play/pause/seek/speed/voice).
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
290
481
|
## PDF Export
|
|
291
482
|
|
|
292
483
|
Export your annotated PDF with all highlights embedded.
|
|
@@ -318,6 +509,7 @@ const handleExport = async () => {
|
|
|
318
509
|
- Freetext notes (background + wrapped text)
|
|
319
510
|
- Images & signatures (embedded PNG/JPG)
|
|
320
511
|
- Freehand drawings (embedded PNG)
|
|
512
|
+
- Shapes (rectangle, circle, arrow)
|
|
321
513
|
|
|
322
514
|
[Full Documentation →](docs/pdf-export.md)
|
|
323
515
|
|
|
@@ -325,28 +517,19 @@ const handleExport = async () => {
|
|
|
325
517
|
|
|
326
518
|
## Component Architecture
|
|
327
519
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
│ │ │ • TextHighlight │ │ │
|
|
342
|
-
│ │ │ • AreaHighlight │ │ │
|
|
343
|
-
│ │ │ • FreetextHighlight │ │ │
|
|
344
|
-
│ │ │ • ImageHighlight │ │ │
|
|
345
|
-
│ │ │ • DrawingHighlight │ │ │
|
|
346
|
-
│ │ └─────────────────────────────────────────┘ │ │
|
|
347
|
-
│ └───────────────────────────────────────────────┘ │
|
|
348
|
-
└─────────────────────────────────────────────────────┘
|
|
349
|
-
```
|
|
520
|
+
The package renders React annotation components into PDF.js page overlay layers:
|
|
521
|
+
|
|
522
|
+
| Layer | Mount point | Purpose |
|
|
523
|
+
|-------|-------------|---------|
|
|
524
|
+
| `.PdfHighlighter__highlight-layer` | Inside PDF.js `.textLayer` | Annotation geometry for text, area, image/signature, drawing, and shape |
|
|
525
|
+
| `.PdfHighlighter__note-layer` | Direct child of PDF.js `.page` | Freetext notes and compact note markers above PDF content |
|
|
526
|
+
| `.PdfHighlighter__config-layer` | Direct child of PDF.js `.page` | Toolbars, style panels, copy buttons, and controls above PDF content |
|
|
527
|
+
|
|
528
|
+
Internal flow diagram:
|
|
529
|
+
|
|
530
|
+
- [Architecture PNG](docs/react-pdf-highlight-plus.png)
|
|
531
|
+
- [Draw.io source](docs/package-build-and-features.drawio)
|
|
532
|
+
- [Draw.io XML copy](docs/package-build-and-features.xml)
|
|
350
533
|
|
|
351
534
|
### Context Hooks
|
|
352
535
|
|