@amafil/react-native-pdf-toolkit 1.0.15 → 1.1.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.
@@ -62,6 +62,27 @@ public class RNPDFPdfViewManagerDelegate<T extends View, U extends BaseViewManag
62
62
  case "enableDoubleTapZoom":
63
63
  mViewManager.setEnableDoubleTapZoom(view, value == null ? false : (boolean) value);
64
64
  break;
65
+ case "annotations":
66
+ mViewManager.setAnnotations(view, value == null ? null : (String) value);
67
+ break;
68
+ case "annotationMode":
69
+ mViewManager.setAnnotationMode(view, value == null ? false : (boolean) value);
70
+ break;
71
+ case "annotationTool":
72
+ mViewManager.setAnnotationTool(view, value == null ? null : (String) value);
73
+ break;
74
+ case "annotationEditable":
75
+ mViewManager.setAnnotationEditable(view, value == null ? false : (boolean) value);
76
+ break;
77
+ case "annotationIdMode":
78
+ mViewManager.setAnnotationIdMode(view, value == null ? null : (String) value);
79
+ break;
80
+ case "annotationInkColor":
81
+ mViewManager.setAnnotationInkColor(view, value == null ? null : (String) value);
82
+ break;
83
+ case "annotationInkThickness":
84
+ mViewManager.setAnnotationInkThickness(view, value == null ? 0f : ((Double) value).floatValue());
85
+ break;
65
86
  case "enableAntialiasing":
66
87
  mViewManager.setEnableAntialiasing(view, value == null ? false : (boolean) value);
67
88
  break;
@@ -87,6 +108,15 @@ public class RNPDFPdfViewManagerDelegate<T extends View, U extends BaseViewManag
87
108
  case "setNativePage":
88
109
  mViewManager.setNativePage(view, args.getInt(0));
89
110
  break;
111
+ case "saveAnnotations":
112
+ mViewManager.saveAnnotations(view);
113
+ break;
114
+ case "deleteSelectedAnnotation":
115
+ mViewManager.deleteSelectedAnnotation(view);
116
+ break;
117
+ case "deleteAllAnnotations":
118
+ mViewManager.deleteAllAnnotations(view);
119
+ break;
90
120
  }
91
121
  }
92
122
  }
@@ -26,10 +26,20 @@ public interface RNPDFPdfViewManagerInterface<T extends View> {
26
26
  void setEnableRTL(T view, boolean value);
27
27
  void setEnableAnnotationRendering(T view, boolean value);
28
28
  void setEnableDoubleTapZoom(T view, boolean value);
29
+ void setAnnotations(T view, @Nullable String value);
30
+ void setAnnotationMode(T view, boolean value);
31
+ void setAnnotationTool(T view, @Nullable String value);
32
+ void setAnnotationEditable(T view, boolean value);
33
+ void setAnnotationIdMode(T view, @Nullable String value);
34
+ void setAnnotationInkColor(T view, @Nullable String value);
35
+ void setAnnotationInkThickness(T view, float value);
29
36
  void setEnableAntialiasing(T view, boolean value);
30
37
  void setFitPolicy(T view, int value);
31
38
  void setSpacing(T view, int value);
32
39
  void setPassword(T view, @Nullable String value);
33
40
  void setSinglePage(T view, boolean value);
34
41
  void setNativePage(T view, int page);
42
+ void saveAnnotations(T view);
43
+ void deleteSelectedAnnotation(T view);
44
+ void deleteAllAnnotations(T view);
35
45
  }
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+
3
+ const LEGACY_MARKUP_TYPES = new Set(['underline', 'strikeout']);
4
+
5
+ export function normalizeAnnotation(annotation) {
6
+ if (!annotation || typeof annotation !== 'object' || Array.isArray(annotation)) {
7
+ return annotation;
8
+ }
9
+
10
+ if (!LEGACY_MARKUP_TYPES.has(annotation.type)) {
11
+ return annotation;
12
+ }
13
+
14
+ return {
15
+ ...annotation,
16
+ type: 'highlight',
17
+ };
18
+ }
19
+
20
+ export function normalizeAnnotationPayload(payload) {
21
+ if (!payload || typeof payload !== 'object') {
22
+ return payload;
23
+ }
24
+
25
+ if (Array.isArray(payload)) {
26
+ return payload.map(normalizeAnnotation);
27
+ }
28
+
29
+ if (!Array.isArray(payload.annotations)) {
30
+ return payload;
31
+ }
32
+
33
+ return {
34
+ ...payload,
35
+ annotations: payload.annotations.map(normalizeAnnotation),
36
+ };
37
+ }
38
+
39
+ export function joinAnnotationMessagePayload(messageParts, startIndex = 1) {
40
+ if (!Array.isArray(messageParts) || messageParts.length <= startIndex) {
41
+ return '';
42
+ }
43
+
44
+ return messageParts.slice(startIndex).join('|');
45
+ }
46
+
47
+ export function parseAnnotationMessagePayload(messageParts, startIndex = 1) {
48
+ const payload = joinAnnotationMessagePayload(messageParts, startIndex);
49
+
50
+ if (!payload) {
51
+ return null;
52
+ }
53
+
54
+ try {
55
+ return normalizeAnnotationPayload(JSON.parse(payload));
56
+ } catch (error) {
57
+ return payload;
58
+ }
59
+ }
60
+
61
+ export function stringifyAnnotationDocument(document, onError) {
62
+ if (!document) {
63
+ return null;
64
+ }
65
+
66
+ try {
67
+ return JSON.stringify(normalizeAnnotationPayload(document));
68
+ } catch (error) {
69
+ if (onError) {
70
+ onError(error);
71
+ }
72
+ return null;
73
+ }
74
+ }
@@ -30,6 +30,13 @@
30
30
  fitPolicy: ?Int32,
31
31
  spacing: ?Int32,
32
32
  password: ?string,
33
+ annotations: ?string,
34
+ annotationMode: ?boolean,
35
+ annotationTool: ?string,
36
+ annotationEditable: ?boolean,
37
+ annotationIdMode: ?string,
38
+ annotationInkColor: ?string,
39
+ annotationInkThickness: ?Float,
33
40
  onChange: ?BubblingEventHandler<ChangeEvent>,
34
41
  singlePage: ?boolean,
35
42
  |}>;
@@ -47,10 +54,19 @@
47
54
  +stopNativeAutoScroll: (
48
55
  viewRef: React.ElementRef<ComponentType>,
49
56
  ) => void;
57
+ +saveAnnotations: (
58
+ viewRef: React.ElementRef<ComponentType>,
59
+ ) => void;
60
+ +deleteSelectedAnnotation: (
61
+ viewRef: React.ElementRef<ComponentType>,
62
+ ) => void;
63
+ +deleteAllAnnotations: (
64
+ viewRef: React.ElementRef<ComponentType>,
65
+ ) => void;
50
66
  }
51
67
 
52
68
  export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
53
- supportedCommands: ['setNativePage', 'startNativeAutoScroll', 'stopNativeAutoScroll'],
69
+ supportedCommands: ['setNativePage', 'startNativeAutoScroll', 'stopNativeAutoScroll', 'saveAnnotations', 'deleteSelectedAnnotation', 'deleteAllAnnotations'],
54
70
  });
55
71
 
56
72
  export default codegenNativeComponent<NativeProps>('RNPDFPdfView');
package/index.d.ts CHANGED
@@ -27,6 +27,68 @@ export type Source = {
27
27
  method?: string;
28
28
  };
29
29
 
30
+ export type AnnotationRotation = 0 | 90 | 180 | 270;
31
+ export type AnnotationIdMode = 'auto' | 'manual';
32
+ export type AnnotationTool = 'select' | 'ink' | 'text' | 'highlight';
33
+ export type AnnotationTextAlign = 'left' | 'center' | 'right';
34
+
35
+ export type AnnotationPoint = {
36
+ x: number,
37
+ y: number,
38
+ pressure?: number,
39
+ };
40
+
41
+ export type AnnotationBounds = {
42
+ x: number,
43
+ y: number,
44
+ width: number,
45
+ height: number,
46
+ };
47
+
48
+ export type AnnotationStyle = {
49
+ color?: string,
50
+ thickness?: number,
51
+ fontFamily?: string,
52
+ fontSize?: number,
53
+ textAlign?: AnnotationTextAlign,
54
+ rotation?: AnnotationRotation,
55
+ };
56
+
57
+ export type AnnotationBase = {
58
+ id: string,
59
+ page: number,
60
+ locked?: boolean,
61
+ createdAt?: number,
62
+ updatedAt?: number,
63
+ };
64
+
65
+ export type InkAnnotation = AnnotationBase & {
66
+ type: 'ink',
67
+ points: AnnotationPoint[],
68
+ style?: AnnotationStyle,
69
+ };
70
+
71
+ export type TextAnnotation = AnnotationBase & {
72
+ type: 'text',
73
+ bounds: AnnotationBounds,
74
+ text: string,
75
+ style?: AnnotationStyle,
76
+ };
77
+
78
+ export type MarkupAnnotation = AnnotationBase & {
79
+ type: 'highlight',
80
+ bounds: AnnotationBounds,
81
+ style?: AnnotationStyle,
82
+ };
83
+
84
+ export type Annotation = InkAnnotation | TextAnnotation | MarkupAnnotation;
85
+
86
+ export type AnnotationDocument = {
87
+ editable?: boolean,
88
+ idMode?: AnnotationIdMode,
89
+ annotations: Annotation[],
90
+ };
91
+
30
92
  export type TextSelectionChangeEvent = {
31
93
  nativeEvent:
32
94
  | {
@@ -58,6 +120,34 @@ export interface PdfProps {
58
120
  enableRTL?: boolean,
59
121
  enableAnnotationRendering?: boolean,
60
122
  enableDoubleTapZoom?: boolean;
123
+ /**
124
+ * Initial annotation document to render in the overlay.
125
+ */
126
+ annotations?: AnnotationDocument,
127
+ /**
128
+ * Enable annotation editing mode.
129
+ */
130
+ annotationMode?: boolean,
131
+ /**
132
+ * Active tool used while annotation editing is enabled.
133
+ */
134
+ annotationTool?: AnnotationTool,
135
+ /**
136
+ * Allow in-place annotation edits.
137
+ */
138
+ annotationEditable?: boolean,
139
+ /**
140
+ * Controls how annotation IDs are generated and preserved.
141
+ */
142
+ annotationIdMode?: AnnotationIdMode,
143
+ /**
144
+ * Default color applied to newly created ink annotations.
145
+ */
146
+ annotationInkColor?: string,
147
+ /**
148
+ * Default thickness applied to newly created ink annotations.
149
+ */
150
+ annotationInkThickness?: number,
61
151
  /**
62
152
  * Only works on iOS. Defaults to `true`.
63
153
  */
@@ -80,11 +170,22 @@ export interface PdfProps {
80
170
  onPressLink?: (url: string) => void,
81
171
  onAutoScrollEnd?: () => void,
82
172
  onTextSelectionChange?: (event: TextSelectionChangeEvent) => void,
83
- onAutoScrollEnd?: () => void,
84
173
  }
85
174
 
86
175
  export interface PdfRef {
87
176
  setPage(pageNumber: number): void
177
+ /**
178
+ * Resolves with the current annotation document serialized by native code.
179
+ */
180
+ saveAnnotations(): Promise<AnnotationDocument>
181
+ /**
182
+ * Deletes the currently selected custom annotation.
183
+ */
184
+ deleteSelectedAnnotation(): void
185
+ /**
186
+ * Deletes all custom annotations in the current overlay draft.
187
+ */
188
+ deleteAllAnnotations(): void
88
189
  /**
89
190
  * Start smooth automatic vertical scrolling using the display refresh rate.
90
191
  * @param dpPerSecond - Scroll speed in density-independent pixels (dp) per second (default: 15). Produces consistent physical speed across screen densities.
package/index.js CHANGED
@@ -23,6 +23,11 @@ import PdfViewNativeComponent, {
23
23
  import ReactNativeBlobUtil from 'react-native-blob-util'
24
24
  import {ViewPropTypes} from 'deprecated-react-native-prop-types';
25
25
  const SHA1 = require('crypto-js/sha1');
26
+ import {
27
+ joinAnnotationMessagePayload,
28
+ parseAnnotationMessagePayload,
29
+ stringifyAnnotationDocument,
30
+ } from './annotationDocumentUtils';
26
31
  import PdfView from './PdfView';
27
32
 
28
33
  export default class Pdf extends Component {
@@ -57,6 +62,13 @@ export default class Pdf extends Component {
57
62
  fitPolicy: PropTypes.number,
58
63
  trustAllCerts: PropTypes.bool,
59
64
  singlePage: PropTypes.bool,
65
+ annotations: PropTypes.object,
66
+ annotationMode: PropTypes.bool,
67
+ annotationTool: PropTypes.oneOf(['select', 'ink', 'text', 'highlight']),
68
+ annotationEditable: PropTypes.bool,
69
+ annotationIdMode: PropTypes.oneOf(['auto', 'manual']),
70
+ annotationInkColor: PropTypes.string,
71
+ annotationInkThickness: PropTypes.number,
60
72
  onLoadComplete: PropTypes.func,
61
73
  onPageChanged: PropTypes.func,
62
74
  onError: PropTypes.func,
@@ -96,6 +108,13 @@ export default class Pdf extends Component {
96
108
  trustAllCerts: true,
97
109
  usePDFKit: true,
98
110
  singlePage: false,
111
+ annotations: null,
112
+ annotationMode: false,
113
+ annotationTool: 'select',
114
+ annotationEditable: true,
115
+ annotationIdMode: 'auto',
116
+ annotationInkColor: '#111111',
117
+ annotationInkThickness: 2,
99
118
  onLoadProgress: (percent) => {
100
119
  },
101
120
  onLoadComplete: (numberOfPages, path) => {
@@ -127,6 +146,7 @@ export default class Pdf extends Component {
127
146
  };
128
147
 
129
148
  this.lastRNBFTask = null;
149
+ this._annotationSavePromise = null;
130
150
 
131
151
  }
132
152
 
@@ -156,6 +176,10 @@ export default class Pdf extends Component {
156
176
  componentWillUnmount() {
157
177
  this._mounted = false;
158
178
  this.stopAutoScroll();
179
+ if (this._annotationSavePromise) {
180
+ this._annotationSavePromise.reject(new Error('Pdf unmounted before annotation save completed'));
181
+ this._annotationSavePromise = null;
182
+ }
159
183
  if (this.lastRNBFTask) {
160
184
  // this.lastRNBFTask.cancel(err => {
161
185
  // });
@@ -372,6 +396,70 @@ export default class Pdf extends Component {
372
396
 
373
397
  }
374
398
 
399
+ saveAnnotations() {
400
+ if (this._annotationSavePromise) {
401
+ return Promise.reject(new Error('An annotation save is already in progress'));
402
+ }
403
+
404
+ return new Promise((resolve, reject) => {
405
+ if (!this._root) {
406
+ reject(new Error('Pdf is not mounted'));
407
+ return;
408
+ }
409
+
410
+ this._annotationSavePromise = {resolve, reject};
411
+
412
+ if (!!global?.nativeFabricUIManager) {
413
+ if (PdfViewCommands.saveAnnotations) {
414
+ PdfViewCommands.saveAnnotations(this._root);
415
+ } else {
416
+ this._annotationSavePromise = null;
417
+ reject(new Error('Annotation save command is not available'));
418
+ }
419
+ } else {
420
+ const ReactNative = require('react-native');
421
+ try {
422
+ ReactNative.UIManager.dispatchViewManagerCommand(
423
+ ReactNative.findNodeHandle(this._root),
424
+ 'saveAnnotations',
425
+ [],
426
+ );
427
+ } catch (error) {
428
+ this._annotationSavePromise = null;
429
+ reject(error);
430
+ }
431
+ }
432
+ });
433
+ }
434
+
435
+ deleteSelectedAnnotation() {
436
+ this._dispatchAnnotationCommand('deleteSelectedAnnotation', PdfViewCommands.deleteSelectedAnnotation);
437
+ }
438
+
439
+ deleteAllAnnotations() {
440
+ this._dispatchAnnotationCommand('deleteAllAnnotations', PdfViewCommands.deleteAllAnnotations);
441
+ }
442
+
443
+ _dispatchAnnotationCommand(commandName, fabricCommand) {
444
+ if (!this._root) {
445
+ return;
446
+ }
447
+
448
+ if (!!global?.nativeFabricUIManager) {
449
+ if (fabricCommand) {
450
+ fabricCommand(this._root);
451
+ }
452
+ return;
453
+ }
454
+
455
+ const ReactNative = require('react-native');
456
+ ReactNative.UIManager.dispatchViewManagerCommand(
457
+ ReactNative.findNodeHandle(this._root),
458
+ commandName,
459
+ [],
460
+ );
461
+ }
462
+
375
463
  startAutoScroll( dpPerSecond = 15, resumeDelay = 3000 ) {
376
464
  this._isAutoScrollActive = true;
377
465
  if (!!global?.nativeFabricUIManager) {
@@ -465,6 +553,20 @@ export default class Pdf extends Component {
465
553
  } else if (message[0] === 'autoScrollEnd') {
466
554
  this._isAutoScrollActive = false;
467
555
  this.props.onAutoScrollEnd && this.props.onAutoScrollEnd();
556
+ } else if (message[0] === 'annotationSaveComplete') {
557
+ const annotationDocument = parseAnnotationMessagePayload(message);
558
+
559
+ if (this._annotationSavePromise) {
560
+ this._annotationSavePromise.resolve(annotationDocument);
561
+ this._annotationSavePromise = null;
562
+ }
563
+ } else if (message[0] === 'annotationSaveError') {
564
+ const annotationError = joinAnnotationMessagePayload(message);
565
+
566
+ if (this._annotationSavePromise) {
567
+ this._annotationSavePromise.reject(new Error(annotationError || 'Annotation save failed'));
568
+ this._annotationSavePromise = null;
569
+ }
468
570
  }
469
571
  }
470
572
 
@@ -476,7 +578,13 @@ export default class Pdf extends Component {
476
578
 
477
579
  };
478
580
 
581
+ _getNativeAnnotations = () => {
582
+ return stringifyAnnotationDocument(this.props.annotations, this._onError);
583
+ };
584
+
479
585
  render() {
586
+ const nativeAnnotations = this._getNativeAnnotations();
587
+
480
588
  if (Platform.OS === "android" || Platform.OS === "ios" || Platform.OS === "windows") {
481
589
  return (
482
590
  <View style={[this.props.style,{overflow: 'hidden'}]}>
@@ -492,6 +600,7 @@ export default class Pdf extends Component {
492
600
  <PdfCustom
493
601
  ref={component => (this._root = component)}
494
602
  {...this.props}
603
+ annotations={nativeAnnotations}
495
604
  style={[{flex:1,backgroundColor: '#EEE'}, this.props.style]}
496
605
  path={this.state.path}
497
606
  onChange={this._onChange}
@@ -501,6 +610,7 @@ export default class Pdf extends Component {
501
610
  <PdfCustom
502
611
  ref={component => (this._root = component)}
503
612
  {...this.props}
613
+ annotations={nativeAnnotations}
504
614
  style={[{backgroundColor: '#EEE',overflow: 'hidden'}, this.props.style]}
505
615
  path={this.state.path}
506
616
  onChange={this._onChange}
package/index.js.flow CHANGED
@@ -24,6 +24,68 @@ export type Source = {
24
24
  uri: string
25
25
  };
26
26
 
27
+ export type AnnotationRotation = 0 | 90 | 180 | 270;
28
+ export type AnnotationIdMode = 'auto' | 'manual';
29
+ export type AnnotationTool = 'select' | 'ink' | 'text' | 'highlight';
30
+ export type AnnotationTextAlign = 'left' | 'center' | 'right';
31
+
32
+ export type AnnotationPoint = {
33
+ x: number,
34
+ y: number,
35
+ pressure?: number,
36
+ };
37
+
38
+ export type AnnotationBounds = {
39
+ x: number,
40
+ y: number,
41
+ width: number,
42
+ height: number,
43
+ };
44
+
45
+ export type AnnotationStyle = {
46
+ color?: string,
47
+ thickness?: number,
48
+ fontFamily?: string,
49
+ fontSize?: number,
50
+ textAlign?: AnnotationTextAlign,
51
+ rotation?: AnnotationRotation,
52
+ };
53
+
54
+ export type AnnotationBase = {
55
+ id: string,
56
+ page: number,
57
+ locked?: boolean,
58
+ createdAt?: number,
59
+ updatedAt?: number,
60
+ };
61
+
62
+ export type InkAnnotation = AnnotationBase & {
63
+ type: 'ink',
64
+ points: Array<AnnotationPoint>,
65
+ style?: AnnotationStyle,
66
+ };
67
+
68
+ export type TextAnnotation = AnnotationBase & {
69
+ type: 'text',
70
+ bounds: AnnotationBounds,
71
+ text: string,
72
+ style?: AnnotationStyle,
73
+ };
74
+
75
+ export type MarkupAnnotation = AnnotationBase & {
76
+ type: 'highlight',
77
+ bounds: AnnotationBounds,
78
+ style?: AnnotationStyle,
79
+ };
80
+
81
+ export type Annotation = InkAnnotation | TextAnnotation | MarkupAnnotation;
82
+
83
+ export type AnnotationDocument = {
84
+ editable?: boolean,
85
+ idMode?: AnnotationIdMode,
86
+ annotations: Array<Annotation>,
87
+ };
88
+
27
89
  export type TableContent = {
28
90
  children: TableContent[],
29
91
  mNativePtr: number,
@@ -33,6 +95,13 @@ export type TableContent = {
33
95
 
34
96
  export type Props = {
35
97
  renderActivityIndicator?: (progress: number) => Node,
98
+ annotations?: AnnotationDocument,
99
+ annotationMode?: boolean,
100
+ annotationTool?: AnnotationTool,
101
+ annotationEditable?: boolean,
102
+ annotationIdMode?: AnnotationIdMode,
103
+ annotationInkColor?: string,
104
+ annotationInkThickness?: number,
36
105
  enableAnnotationRendering?: boolean,
37
106
  enableAntialiasing?: boolean,
38
107
  enablePaging?: boolean,
@@ -64,4 +133,7 @@ export type Props = {
64
133
 
65
134
  declare export default class Pdf extends Component<Props> {
66
135
  setPage: (pageNumber: number) => void;
136
+ saveAnnotations: () => Promise<AnnotationDocument>;
137
+ deleteSelectedAnnotation: () => void;
138
+ deleteAllAnnotations: () => void;
67
139
  }
@@ -49,6 +49,13 @@ UIView
49
49
  @property(nonatomic) BOOL enableRTL;
50
50
  @property(nonatomic) BOOL enableAnnotationRendering;
51
51
  @property(nonatomic) BOOL enableDoubleTapZoom;
52
+ @property(nonatomic, strong) NSString *annotations;
53
+ @property(nonatomic) BOOL annotationMode;
54
+ @property(nonatomic, strong) NSString *annotationTool;
55
+ @property(nonatomic) BOOL annotationEditable;
56
+ @property(nonatomic, strong) NSString *annotationIdMode;
57
+ @property(nonatomic, strong) NSString *annotationInkColor;
58
+ @property(nonatomic) float annotationInkThickness;
52
59
  @property(nonatomic) int fitPolicy;
53
60
  @property(nonatomic) int spacing;
54
61
  @property(nonatomic, strong) NSString *password;
@@ -56,6 +63,10 @@ UIView
56
63
 
57
64
  @property(nonatomic, copy) RCTBubblingEventBlock onChange;
58
65
 
66
+ - (void)saveAnnotations;
67
+ - (void)deleteSelectedAnnotation;
68
+ - (void)deleteAllAnnotations;
69
+
59
70
  @property(nonatomic, strong) NSString *selectedText;
60
71
  @property(nonatomic) BOOL enableTextSelection;
61
72
  @property(nonatomic, strong) PDFSelection *currentPDFSelection;