hazo_pdf 1.2.1 → 1.3.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 +233 -0
- package/dist/index.d.ts +43 -12
- package/dist/index.js +77 -10
- package/dist/index.js.map +1 -1
- package/dist/styles/full.css +49 -0
- package/dist/styles/full.css.map +1 -1
- package/dist/styles/index.css +49 -0
- package/dist/styles/index.css.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A React component library for viewing and annotating PDF documents with support
|
|
|
6
6
|
|
|
7
7
|
- 📄 **PDF Viewing** - Render PDF documents with customizable zoom levels
|
|
8
8
|
- ✏️ **Annotations** - Square and FreeText annotation tools
|
|
9
|
+
- 🔍 **Programmatic Highlights** - Ref-based API for creating and managing highlights programmatically
|
|
9
10
|
- 🎨 **Customizable Styling** - Extensive configuration options via INI file
|
|
10
11
|
- ⏰ **Timestamp Support** - Automatic timestamp appending to annotations
|
|
11
12
|
- 🏷️ **Custom Stamps** - Add quick-insert stamps via right-click menu
|
|
@@ -413,6 +414,204 @@ See `config/hazo_pdf_config.ini` in the project root for all available configura
|
|
|
413
414
|
|
|
414
415
|
---
|
|
415
416
|
|
|
417
|
+
## Programmatic Highlight API
|
|
418
|
+
|
|
419
|
+
The PDF viewer exposes a ref-based API for programmatically creating, removing, and managing highlights. This allows external code to control highlights without user interaction.
|
|
420
|
+
|
|
421
|
+
### Basic Usage
|
|
422
|
+
|
|
423
|
+
```tsx
|
|
424
|
+
import { PdfViewer, PdfViewerRef } from 'hazo_pdf';
|
|
425
|
+
import { useRef } from 'react';
|
|
426
|
+
import 'hazo_pdf/styles.css';
|
|
427
|
+
|
|
428
|
+
function HighlightExample() {
|
|
429
|
+
const viewer_ref = useRef<PdfViewerRef>(null);
|
|
430
|
+
|
|
431
|
+
const add_highlight = () => {
|
|
432
|
+
// Highlight region on page 0 at PDF coordinates [100, 500, 300, 550]
|
|
433
|
+
const id = viewer_ref.current?.highlight_region(0, [100, 500, 300, 550], {
|
|
434
|
+
border_color: '#FF0000',
|
|
435
|
+
background_color: '#FFFF00',
|
|
436
|
+
background_opacity: 0.4
|
|
437
|
+
});
|
|
438
|
+
console.log('Created highlight:', id);
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const remove_specific_highlight = (id: string) => {
|
|
442
|
+
const removed = viewer_ref.current?.remove_highlight(id);
|
|
443
|
+
console.log('Highlight removed:', removed);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
const clear_all = () => {
|
|
447
|
+
viewer_ref.current?.clear_all_highlights();
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<div style={{ width: '100%', height: '800px' }}>
|
|
452
|
+
<button onClick={add_highlight}>Add Highlight</button>
|
|
453
|
+
<button onClick={clear_all}>Clear All Highlights</button>
|
|
454
|
+
|
|
455
|
+
<PdfViewer
|
|
456
|
+
ref={viewer_ref}
|
|
457
|
+
url="/document.pdf"
|
|
458
|
+
/>
|
|
459
|
+
</div>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### PdfViewerRef Interface
|
|
465
|
+
|
|
466
|
+
The ref exposes three methods for highlight management:
|
|
467
|
+
|
|
468
|
+
#### `highlight_region(page_index, rect, options?)`
|
|
469
|
+
|
|
470
|
+
Creates a new highlight annotation on the specified page.
|
|
471
|
+
|
|
472
|
+
**Parameters:**
|
|
473
|
+
- `page_index` (number): Zero-based page index where the highlight should appear
|
|
474
|
+
- `rect` ([number, number, number, number]): Rectangle coordinates in PDF space [x1, y1, x2, y2]
|
|
475
|
+
- `options` (HighlightOptions, optional): Styling options
|
|
476
|
+
|
|
477
|
+
**Returns:** `string` - The unique ID of the created highlight annotation
|
|
478
|
+
|
|
479
|
+
**HighlightOptions:**
|
|
480
|
+
```typescript
|
|
481
|
+
{
|
|
482
|
+
border_color?: string; // Hex color (e.g., "#FF0000")
|
|
483
|
+
background_color?: string; // Hex color (e.g., "#FFFF00")
|
|
484
|
+
background_opacity?: number; // 0-1 (e.g., 0.4)
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
**Example:**
|
|
489
|
+
```tsx
|
|
490
|
+
const id = viewer_ref.current?.highlight_region(
|
|
491
|
+
0, // Page 0
|
|
492
|
+
[100, 500, 300, 550], // PDF coordinates
|
|
493
|
+
{
|
|
494
|
+
border_color: '#FF0000',
|
|
495
|
+
background_color: '#FFFF00',
|
|
496
|
+
background_opacity: 0.4
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
#### `remove_highlight(id)`
|
|
502
|
+
|
|
503
|
+
Removes a specific highlight by its ID.
|
|
504
|
+
|
|
505
|
+
**Parameters:**
|
|
506
|
+
- `id` (string): The highlight ID returned from `highlight_region()`
|
|
507
|
+
|
|
508
|
+
**Returns:** `boolean` - `true` if the highlight was found and removed, `false` otherwise
|
|
509
|
+
|
|
510
|
+
**Example:**
|
|
511
|
+
```tsx
|
|
512
|
+
const removed = viewer_ref.current?.remove_highlight('highlight-123');
|
|
513
|
+
if (removed) {
|
|
514
|
+
console.log('Highlight removed successfully');
|
|
515
|
+
}
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
#### `clear_all_highlights()`
|
|
519
|
+
|
|
520
|
+
Removes all highlights created via the `highlight_region()` API. Does not affect user-created annotations.
|
|
521
|
+
|
|
522
|
+
**Example:**
|
|
523
|
+
```tsx
|
|
524
|
+
viewer_ref.current?.clear_all_highlights();
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Advanced Example: Search Results Highlighting
|
|
528
|
+
|
|
529
|
+
A common use case is highlighting search results in a PDF:
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
import { useState, useRef } from 'react';
|
|
533
|
+
import { PdfViewer, PdfViewerRef } from 'hazo_pdf';
|
|
534
|
+
import 'hazo_pdf/styles.css';
|
|
535
|
+
|
|
536
|
+
interface SearchResult {
|
|
537
|
+
page: number;
|
|
538
|
+
rect: [number, number, number, number];
|
|
539
|
+
text: string;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function SearchableViewer() {
|
|
543
|
+
const viewer_ref = useRef<PdfViewerRef>(null);
|
|
544
|
+
const [highlight_ids, set_highlight_ids] = useState<string[]>([]);
|
|
545
|
+
|
|
546
|
+
// Simulated search results
|
|
547
|
+
const search_results: SearchResult[] = [
|
|
548
|
+
{ page: 0, rect: [100, 500, 300, 550], text: 'Result 1' },
|
|
549
|
+
{ page: 0, rect: [100, 400, 300, 450], text: 'Result 2' },
|
|
550
|
+
{ page: 1, rect: [150, 600, 350, 650], text: 'Result 3' }
|
|
551
|
+
];
|
|
552
|
+
|
|
553
|
+
const highlight_search_results = () => {
|
|
554
|
+
// Clear previous highlights
|
|
555
|
+
viewer_ref.current?.clear_all_highlights();
|
|
556
|
+
|
|
557
|
+
// Add new highlights
|
|
558
|
+
const ids = search_results.map(result =>
|
|
559
|
+
viewer_ref.current?.highlight_region(result.page, result.rect, {
|
|
560
|
+
border_color: '#3B82F6',
|
|
561
|
+
background_color: '#DBEAFE',
|
|
562
|
+
background_opacity: 0.3
|
|
563
|
+
})
|
|
564
|
+
).filter(Boolean) as string[];
|
|
565
|
+
|
|
566
|
+
set_highlight_ids(ids);
|
|
567
|
+
};
|
|
568
|
+
|
|
569
|
+
const clear_search = () => {
|
|
570
|
+
viewer_ref.current?.clear_all_highlights();
|
|
571
|
+
set_highlight_ids([]);
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
return (
|
|
575
|
+
<div style={{ width: '100%', height: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
576
|
+
<div style={{ padding: '1rem', background: '#f0f0f0' }}>
|
|
577
|
+
<button onClick={highlight_search_results}>
|
|
578
|
+
Highlight Search Results ({search_results.length})
|
|
579
|
+
</button>
|
|
580
|
+
<button onClick={clear_search}>Clear Highlights</button>
|
|
581
|
+
<span>Highlighted: {highlight_ids.length} results</span>
|
|
582
|
+
</div>
|
|
583
|
+
|
|
584
|
+
<div style={{ flex: 1 }}>
|
|
585
|
+
<PdfViewer
|
|
586
|
+
ref={viewer_ref}
|
|
587
|
+
url="/document.pdf"
|
|
588
|
+
/>
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
### Coordinate System Notes
|
|
596
|
+
|
|
597
|
+
- Highlights use PDF coordinate space (points), not screen pixels
|
|
598
|
+
- PDF coordinates start at the bottom-left corner (Y increases upward)
|
|
599
|
+
- The `rect` parameter format is `[x1, y1, x2, y2]` where:
|
|
600
|
+
- `x1, y1`: Bottom-left corner
|
|
601
|
+
- `x2, y2`: Top-right corner
|
|
602
|
+
- If you need to convert screen coordinates to PDF coordinates, you'll need to use the PDF page viewport (see the test app for examples)
|
|
603
|
+
|
|
604
|
+
### Styling Defaults
|
|
605
|
+
|
|
606
|
+
If no `options` are provided to `highlight_region()`, the highlight uses default colors from the configuration file:
|
|
607
|
+
- `border_color`: From `highlight_border_color` config setting
|
|
608
|
+
- `background_color`: From `highlight_fill_color` config setting
|
|
609
|
+
- `background_opacity`: From `highlight_fill_opacity` config setting
|
|
610
|
+
|
|
611
|
+
You can override any or all of these on a per-highlight basis.
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
416
615
|
## API Reference
|
|
417
616
|
|
|
418
617
|
### PdfViewer Props
|
|
@@ -573,6 +772,40 @@ interface PdfAnnotation {
|
|
|
573
772
|
}
|
|
574
773
|
```
|
|
575
774
|
|
|
775
|
+
**Note:** Highlights created via the programmatic API (`PdfViewerRef.highlight_region()`) will have `type: 'Highlight'` and `flags: 'api_highlight'` to distinguish them from user-created annotations.
|
|
776
|
+
|
|
777
|
+
### PdfViewerRef Interface
|
|
778
|
+
|
|
779
|
+
Interface for the ref exposed by `PdfViewer`. Use with `useRef<PdfViewerRef>()` to access programmatic highlight methods.
|
|
780
|
+
|
|
781
|
+
```typescript
|
|
782
|
+
interface PdfViewerRef {
|
|
783
|
+
highlight_region: (
|
|
784
|
+
page_index: number,
|
|
785
|
+
rect: [number, number, number, number],
|
|
786
|
+
options?: HighlightOptions
|
|
787
|
+
) => string;
|
|
788
|
+
|
|
789
|
+
remove_highlight: (id: string) => boolean;
|
|
790
|
+
|
|
791
|
+
clear_all_highlights: () => void;
|
|
792
|
+
}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
See [Programmatic Highlight API](#programmatic-highlight-api) for detailed usage.
|
|
796
|
+
|
|
797
|
+
### HighlightOptions Interface
|
|
798
|
+
|
|
799
|
+
Options for customizing highlights created via the API.
|
|
800
|
+
|
|
801
|
+
```typescript
|
|
802
|
+
interface HighlightOptions {
|
|
803
|
+
border_color?: string; // Hex format (e.g., "#FF0000")
|
|
804
|
+
background_color?: string; // Hex format (e.g., "#FFFF00")
|
|
805
|
+
background_opacity?: number; // 0-1 (e.g., 0.4)
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
576
809
|
### PDFDocumentProxy
|
|
577
810
|
|
|
578
811
|
Type from `pdfjs-dist`. Contains PDF metadata and page proxies.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import React__default from 'react';
|
|
2
3
|
import { PDFDocumentProxy, PDFPageProxy } from 'pdfjs-dist';
|
|
3
4
|
|
|
4
5
|
/**
|
|
@@ -403,18 +404,48 @@ interface MetadataInput {
|
|
|
403
404
|
/** Array of footer items */
|
|
404
405
|
footer: MetadataFooterItem[];
|
|
405
406
|
}
|
|
406
|
-
|
|
407
407
|
/**
|
|
408
|
-
*
|
|
409
|
-
* Main component for displaying and interacting with PDF documents
|
|
410
|
-
* Integrates PDF rendering, annotation overlay, and layout management
|
|
408
|
+
* Options for programmatic highlight creation via PdfViewerRef
|
|
411
409
|
*/
|
|
410
|
+
interface HighlightOptions {
|
|
411
|
+
/** Border color in hex format (default: from config highlight_border_color) */
|
|
412
|
+
border_color?: string;
|
|
413
|
+
/** Background/fill color in hex format (default: from config highlight_fill_color) */
|
|
414
|
+
background_color?: string;
|
|
415
|
+
/** Background opacity 0-1 (default: from config highlight_fill_opacity) */
|
|
416
|
+
background_opacity?: number;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Ref interface for programmatic PDF viewer control
|
|
420
|
+
* Use with useRef<PdfViewerRef>() to get access to imperative methods
|
|
421
|
+
*/
|
|
422
|
+
interface PdfViewerRef {
|
|
423
|
+
/**
|
|
424
|
+
* Create a highlight on a specific page region
|
|
425
|
+
* @param page_index - Zero-based page index
|
|
426
|
+
* @param rect - Rectangle coordinates in PDF space [x1, y1, x2, y2]
|
|
427
|
+
* @param options - Optional styling overrides (border_color, background_color, background_opacity)
|
|
428
|
+
* @returns The highlight annotation ID
|
|
429
|
+
*/
|
|
430
|
+
highlight_region: (page_index: number, rect: [number, number, number, number], options?: HighlightOptions) => string;
|
|
431
|
+
/**
|
|
432
|
+
* Remove a specific highlight by ID
|
|
433
|
+
* @param id - The highlight annotation ID returned from highlight_region
|
|
434
|
+
* @returns true if highlight was found and removed, false otherwise
|
|
435
|
+
*/
|
|
436
|
+
remove_highlight: (id: string) => boolean;
|
|
437
|
+
/**
|
|
438
|
+
* Remove all highlights created via the highlight_region API
|
|
439
|
+
* Does not affect user-created annotations
|
|
440
|
+
*/
|
|
441
|
+
clear_all_highlights: () => void;
|
|
442
|
+
}
|
|
412
443
|
|
|
413
444
|
/**
|
|
414
445
|
* PDF Viewer Component
|
|
415
446
|
* Main entry point for PDF viewing and annotation
|
|
416
447
|
*/
|
|
417
|
-
declare const PdfViewer: React.
|
|
448
|
+
declare const PdfViewer: React.ForwardRefExoticComponent<PdfViewerProps & React.RefAttributes<PdfViewerRef>>;
|
|
418
449
|
|
|
419
450
|
/**
|
|
420
451
|
* PDF Viewer Layout Component
|
|
@@ -431,7 +462,7 @@ interface PdfViewerLayoutProps {
|
|
|
431
462
|
annotations: PdfAnnotation[];
|
|
432
463
|
current_tool: 'Square' | 'Highlight' | 'FreeText' | 'CustomBookmark' | null;
|
|
433
464
|
on_annotation_create: (annotation: PdfAnnotation) => void;
|
|
434
|
-
on_context_menu: (e:
|
|
465
|
+
on_context_menu: (e: React__default.MouseEvent, page_index: number, screen_x: number, screen_y: number, mapper: CoordinateMapper) => void;
|
|
435
466
|
on_annotation_click: (annotation: PdfAnnotation, screen_x: number, screen_y: number, mapper: CoordinateMapper) => void;
|
|
436
467
|
on_freetext_click?: (page_index: number, screen_x: number, screen_y: number, mapper: CoordinateMapper) => void;
|
|
437
468
|
background_color?: string;
|
|
@@ -442,7 +473,7 @@ interface PdfViewerLayoutProps {
|
|
|
442
473
|
* PDF Viewer Layout Component
|
|
443
474
|
* Manages page rendering and annotation overlay coordination
|
|
444
475
|
*/
|
|
445
|
-
declare const PdfViewerLayout:
|
|
476
|
+
declare const PdfViewerLayout: React__default.FC<PdfViewerLayoutProps>;
|
|
446
477
|
|
|
447
478
|
/**
|
|
448
479
|
* PDF Page Renderer Component
|
|
@@ -471,7 +502,7 @@ interface PdfPageRendererProps {
|
|
|
471
502
|
* PDF Page Renderer Component
|
|
472
503
|
* Handles rendering of a single PDF page to canvas
|
|
473
504
|
*/
|
|
474
|
-
declare const PdfPageRenderer:
|
|
505
|
+
declare const PdfPageRenderer: React__default.FC<PdfPageRendererProps>;
|
|
475
506
|
|
|
476
507
|
/**
|
|
477
508
|
* Annotation Overlay Component
|
|
@@ -497,7 +528,7 @@ interface AnnotationOverlayProps {
|
|
|
497
528
|
/** Callback when annotation is created */
|
|
498
529
|
on_annotation_create?: (annotation: PdfAnnotation) => void;
|
|
499
530
|
/** Callback when right-click occurs */
|
|
500
|
-
on_context_menu?: (event:
|
|
531
|
+
on_context_menu?: (event: React__default.MouseEvent, screen_x: number, screen_y: number) => void;
|
|
501
532
|
/** Callback when annotation is clicked */
|
|
502
533
|
on_annotation_click?: (annotation: PdfAnnotation, screen_x: number, screen_y: number) => void;
|
|
503
534
|
/** Callback when FreeText tool is active and user clicks on empty area */
|
|
@@ -511,7 +542,7 @@ interface AnnotationOverlayProps {
|
|
|
511
542
|
* Annotation Overlay Component
|
|
512
543
|
* Handles mouse interactions for creating annotations
|
|
513
544
|
*/
|
|
514
|
-
declare const AnnotationOverlay:
|
|
545
|
+
declare const AnnotationOverlay: React__default.FC<AnnotationOverlayProps>;
|
|
515
546
|
|
|
516
547
|
/**
|
|
517
548
|
* PDF Worker Setup
|
|
@@ -729,4 +760,4 @@ declare function load_pdf_config(config_file?: string): PdfViewerConfig;
|
|
|
729
760
|
*/
|
|
730
761
|
declare const default_config: PdfViewerConfig;
|
|
731
762
|
|
|
732
|
-
export { AnnotationOverlay, type CoordinateMapper, type CustomStamp, type MetadataDataItem, type MetadataFooterItem, type MetadataFormatType, type MetadataHeaderItem, type MetadataInput, type PageDimensions, type PdfAnnotation, type PdfBookmark, type PdfDocument, type PdfPage, PdfPageRenderer, PdfViewer, type PdfViewerConfig, PdfViewerLayout, type PdfViewerProps, build_config_from_ini, calculate_rectangle_coords, create_coordinate_mapper, default_config, download_pdf, download_xfdf, export_annotations_to_xfdf, generate_xfdf, get_viewport_dimensions, is_rectangle_too_small, load_pdf_config, load_pdf_config_async, load_pdf_document, parse_color, parse_number, parse_opacity, parse_string, pdf_rect_to_rectangle, rectangle_to_pdf_rect, save_and_download_pdf, save_annotations_to_pdf };
|
|
763
|
+
export { AnnotationOverlay, type CoordinateMapper, type CustomStamp, type HighlightOptions, type MetadataDataItem, type MetadataFooterItem, type MetadataFormatType, type MetadataHeaderItem, type MetadataInput, type PageDimensions, type PdfAnnotation, type PdfBookmark, type PdfDocument, type PdfPage, PdfPageRenderer, PdfViewer, type PdfViewerConfig, PdfViewerLayout, type PdfViewerProps, type PdfViewerRef, build_config_from_ini, calculate_rectangle_coords, create_coordinate_mapper, default_config, download_pdf, download_xfdf, export_annotations_to_xfdf, generate_xfdf, get_viewport_dimensions, is_rectangle_too_small, load_pdf_config, load_pdf_config_async, load_pdf_document, parse_color, parse_number, parse_opacity, parse_string, pdf_rect_to_rectangle, rectangle_to_pdf_rect, save_and_download_pdf, save_annotations_to_pdf };
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-S3AJUZ7D.js";
|
|
9
9
|
|
|
10
10
|
// src/components/pdf_viewer/pdf_viewer.tsx
|
|
11
|
-
import { useState as useState6, useEffect as useEffect6, useRef as useRef7, useCallback as useCallback2 } from "react";
|
|
11
|
+
import { useState as useState6, useEffect as useEffect6, useRef as useRef7, useCallback as useCallback2, forwardRef, useImperativeHandle } from "react";
|
|
12
12
|
import { Save, Undo2 as Undo22, Redo2, PanelRight, ZoomIn, ZoomOut, RotateCcw, Square, Type } from "lucide-react";
|
|
13
13
|
|
|
14
14
|
// src/components/pdf_viewer/pdf_worker_setup.ts
|
|
@@ -725,18 +725,29 @@ var AnnotationOverlay = ({
|
|
|
725
725
|
}
|
|
726
726
|
const get_annotation_props = () => {
|
|
727
727
|
switch (annotation.type) {
|
|
728
|
-
case "Highlight":
|
|
729
|
-
|
|
728
|
+
case "Highlight": {
|
|
729
|
+
let custom_highlight_options = null;
|
|
730
|
+
if (annotation.subject) {
|
|
731
|
+
try {
|
|
732
|
+
custom_highlight_options = JSON.parse(annotation.subject);
|
|
733
|
+
} catch {
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
const highlight_fill_color = custom_highlight_options?.background_color || annotation.color || highlight_config.highlight_fill_color;
|
|
737
|
+
const highlight_fill_opacity = custom_highlight_options?.background_opacity ?? highlight_config.highlight_fill_opacity;
|
|
738
|
+
const highlight_border_color = custom_highlight_options?.border_color || highlight_config.highlight_border_color;
|
|
730
739
|
return {
|
|
731
|
-
fill: hex_to_rgba(
|
|
732
|
-
stroke:
|
|
740
|
+
fill: hex_to_rgba(highlight_fill_color, highlight_fill_opacity),
|
|
741
|
+
stroke: highlight_border_color
|
|
733
742
|
};
|
|
734
|
-
|
|
743
|
+
}
|
|
744
|
+
case "Square": {
|
|
735
745
|
const square_color = annotation.color || square_config.square_fill_color;
|
|
736
746
|
return {
|
|
737
747
|
fill: hex_to_rgba(square_color, square_config.square_fill_opacity),
|
|
738
748
|
stroke: square_config.square_border_color
|
|
739
749
|
};
|
|
750
|
+
}
|
|
740
751
|
default:
|
|
741
752
|
return {
|
|
742
753
|
fill: "rgba(0, 0, 255, 0.2)",
|
|
@@ -2272,7 +2283,7 @@ function load_pdf_config(config_file) {
|
|
|
2272
2283
|
|
|
2273
2284
|
// src/components/pdf_viewer/pdf_viewer.tsx
|
|
2274
2285
|
import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2275
|
-
var PdfViewer = ({
|
|
2286
|
+
var PdfViewer = forwardRef(({
|
|
2276
2287
|
url,
|
|
2277
2288
|
className = "",
|
|
2278
2289
|
scale: initial_scale = 1,
|
|
@@ -2301,7 +2312,7 @@ var PdfViewer = ({
|
|
|
2301
2312
|
show_metadata_button,
|
|
2302
2313
|
show_annotate_button,
|
|
2303
2314
|
on_close
|
|
2304
|
-
}) => {
|
|
2315
|
+
}, ref) => {
|
|
2305
2316
|
const [pdf_document, setPdfDocument] = useState6(null);
|
|
2306
2317
|
const [loading, setLoading] = useState6(true);
|
|
2307
2318
|
const [error, setError] = useState6(null);
|
|
@@ -2661,6 +2672,60 @@ ${suffix_line}`;
|
|
|
2661
2672
|
on_annotation_delete(annotation_id);
|
|
2662
2673
|
}
|
|
2663
2674
|
};
|
|
2675
|
+
useImperativeHandle(ref, () => ({
|
|
2676
|
+
/**
|
|
2677
|
+
* Create a highlight on a specific page region
|
|
2678
|
+
* @param page_index - Zero-based page index
|
|
2679
|
+
* @param rect - Rectangle coordinates in PDF space [x1, y1, x2, y2]
|
|
2680
|
+
* @param options - Optional styling overrides
|
|
2681
|
+
* @returns The highlight annotation ID
|
|
2682
|
+
*/
|
|
2683
|
+
highlight_region: (page_index, rect, options) => {
|
|
2684
|
+
const highlight_config = config_ref.current?.highlight_annotation || default_config.highlight_annotation;
|
|
2685
|
+
const annotation = {
|
|
2686
|
+
id: `highlight_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
2687
|
+
type: "Highlight",
|
|
2688
|
+
page_index,
|
|
2689
|
+
rect,
|
|
2690
|
+
author: "API",
|
|
2691
|
+
date: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2692
|
+
contents: "",
|
|
2693
|
+
color: options?.background_color || highlight_config.highlight_fill_color,
|
|
2694
|
+
flags: "api_highlight"
|
|
2695
|
+
// Marker to identify API-created highlights
|
|
2696
|
+
};
|
|
2697
|
+
if (options) {
|
|
2698
|
+
annotation.subject = JSON.stringify({
|
|
2699
|
+
border_color: options.border_color,
|
|
2700
|
+
background_color: options.background_color,
|
|
2701
|
+
background_opacity: options.background_opacity
|
|
2702
|
+
});
|
|
2703
|
+
}
|
|
2704
|
+
handle_annotation_create(annotation);
|
|
2705
|
+
return annotation.id;
|
|
2706
|
+
},
|
|
2707
|
+
/**
|
|
2708
|
+
* Remove a specific highlight by ID
|
|
2709
|
+
* @param id - The highlight annotation ID
|
|
2710
|
+
* @returns true if highlight was found and removed
|
|
2711
|
+
*/
|
|
2712
|
+
remove_highlight: (id) => {
|
|
2713
|
+
const annotation = annotations.find((a) => a.id === id);
|
|
2714
|
+
if (annotation) {
|
|
2715
|
+
handle_annotation_delete(id);
|
|
2716
|
+
return true;
|
|
2717
|
+
}
|
|
2718
|
+
return false;
|
|
2719
|
+
},
|
|
2720
|
+
/**
|
|
2721
|
+
* Remove all highlights created via the highlight_region API
|
|
2722
|
+
*/
|
|
2723
|
+
clear_all_highlights: () => {
|
|
2724
|
+
const api_highlights = annotations.filter((a) => a.flags === "api_highlight");
|
|
2725
|
+
api_highlights.forEach((a) => handle_annotation_delete(a.id));
|
|
2726
|
+
}
|
|
2727
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2728
|
+
}), [annotations, handle_annotation_create, handle_annotation_delete]);
|
|
2664
2729
|
const handle_undo = useCallback2(() => {
|
|
2665
2730
|
if (history_index > 0) {
|
|
2666
2731
|
history_ref.current.saving = true;
|
|
@@ -2742,9 +2807,10 @@ ${suffix_line}`;
|
|
|
2742
2807
|
const output_filename = `${filename_without_ext}_annotated.pdf`;
|
|
2743
2808
|
const { save_annotations_to_pdf: save_annotations_to_pdf2, download_pdf: download_pdf2 } = await import("./pdf_saver-P2MJN45S.js");
|
|
2744
2809
|
const pdf_bytes = await save_annotations_to_pdf2(url, annotations, output_filename, config_ref.current);
|
|
2745
|
-
download_pdf2(pdf_bytes, output_filename);
|
|
2746
2810
|
if (on_save) {
|
|
2747
2811
|
on_save(pdf_bytes, output_filename);
|
|
2812
|
+
} else {
|
|
2813
|
+
download_pdf2(pdf_bytes, output_filename);
|
|
2748
2814
|
}
|
|
2749
2815
|
} catch (error2) {
|
|
2750
2816
|
console.error("PdfViewer: Error saving PDF:", error2);
|
|
@@ -3271,7 +3337,8 @@ ${suffix_line}`;
|
|
|
3271
3337
|
}
|
|
3272
3338
|
)
|
|
3273
3339
|
] });
|
|
3274
|
-
};
|
|
3340
|
+
});
|
|
3341
|
+
PdfViewer.displayName = "PdfViewer";
|
|
3275
3342
|
|
|
3276
3343
|
// src/utils/xfdf_generator.ts
|
|
3277
3344
|
function format_pdf_date(date) {
|