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 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
- | **Light/Dark Theme** | Eye-friendly dark mode with customizable intensity |
43
- | **Zoom Support** | Full zoom functionality with position-independent data |
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
- enableDrawingCreation={() => drawingMode}
215
- onDrawingComplete={(position, dataUrl) => {
232
+ enableDrawingMode={drawingMode}
233
+ onDrawingComplete={(dataUrl, position, strokes) => {
216
234
  addHighlight({ type: "drawing", position, content: { image: dataUrl } });
217
235
  }}
218
- drawingConfig={{
219
- strokeColor: "#ff0000",
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
- Toggle between light and dark modes with customizable styling for comfortable reading.
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 mode intensity and colors
322
+ // Customize the dark palette
256
323
  <PdfHighlighter
257
324
  pdfDocument={pdfDocument}
258
325
  theme={{
259
326
  mode: "dark",
260
- darkModeInvertIntensity: 0.85, // Softer (0.8-1.0)
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
- **Features:**
272
- - Eye-friendly dark mode using CSS filter inversion
273
- - Customizable inversion intensity (0.8-1.0)
274
- - Preserve original highlight colors in dark mode
275
- - Custom scrollbar styling
276
- - Full theming control for container background
277
-
278
- **Inversion Intensity Guide:**
279
- | Value | Result | Use Case |
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
- │ PdfLoader │
331
- │ Loads PDF document via PDF.js │
332
- │ │
333
- │ ┌───────────────────────────────────────────────┐ │
334
- │ │ PdfHighlighter │ │
335
- │ │ Manages viewer, events, coordinate systems │ │
336
- │ │ │ │
337
- │ │ ┌─────────────────────────────────────────┐ │ │
338
- │ │ │ User-defined HighlightContainer │ │ │
339
- │ │ │ Renders highlights using context hooks │ │ │
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