@amafil/react-native-pdf-toolkit 1.0.11 → 1.1.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.
@@ -54,6 +54,36 @@
54
54
  const float MAX_SCALE = 3.0f;
55
55
  const float MIN_SCALE = 1.0f;
56
56
 
57
+ @class RNPDFAnnotationOverlay;
58
+
59
+ @interface RNPDFAnnotationOverlay : UIView <UITextViewDelegate>
60
+ @property(nonatomic, weak) PDFView *pdfView;
61
+ @property(nonatomic, weak) PDFDocument *pdfDocument;
62
+ @property(nonatomic, assign) BOOL annotationMode;
63
+ @property(nonatomic, assign) BOOL annotationEditable;
64
+ @property(nonatomic, copy) NSString *annotationTool;
65
+ @property(nonatomic, copy) NSString *annotationIdMode;
66
+
67
+ - (void)replaceAnnotationsJSONString:(NSString *)json editable:(BOOL)editable idMode:(NSString *)idMode;
68
+ - (void)setAnnotationMode:(BOOL)annotationMode tool:(NSString *)tool editable:(BOOL)editable idMode:(NSString *)idMode;
69
+ - (void)beginInkAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page;
70
+ - (void)appendInkPointAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page;
71
+ - (void)endInk;
72
+ - (void)beginMarkupAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page type:(NSString *)type;
73
+ - (void)updateMarkupAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page;
74
+ - (void)endMarkup;
75
+ - (void)createTextAnnotationAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page;
76
+ - (NSDictionary *)annotationSelectionHitAtPoint:(CGPoint)point includeHandles:(BOOL)includeHandles;
77
+ - (void)selectAnnotation:(NSDictionary *)annotation;
78
+ - (void)clearSelection;
79
+ - (void)deleteAnnotation:(NSDictionary *)annotation;
80
+ - (void)beginSelectionInteractionAtPoint:(CGPoint)point hit:(NSDictionary *)hit;
81
+ - (void)updateSelectionInteractionAtPoint:(CGPoint)point;
82
+ - (void)endSelectionInteraction;
83
+ - (void)commitTextEditingIfNeeded;
84
+ - (NSString *)serializedDocumentJSONStringWithEditable:(BOOL)editable idMode:(NSString *)idMode;
85
+ @end
86
+
57
87
  @interface RNPDFPdfView() <PDFDocumentDelegate, PDFViewDelegate
58
88
  #ifdef RCT_NEW_ARCH_ENABLED
59
89
  , RCTRNPDFPdfViewViewProtocol
@@ -75,6 +105,8 @@ const float MIN_SCALE = 1.0f;
75
105
  UIPinchGestureRecognizer *_pinchRecognizer;
76
106
  UILongPressGestureRecognizer *_longPressRecognizer;
77
107
  UITapGestureRecognizer *_doubleTapEmptyRecognizer;
108
+ UIPanGestureRecognizer *_annotationPanRecognizer;
109
+ RNPDFAnnotationOverlay *_annotationOverlay;
78
110
 
79
111
  // Autoscroll
80
112
  CADisplayLink *_displayLink;
@@ -157,6 +189,26 @@ using namespace facebook::react;
157
189
  _enableDoubleTapZoom = newProps.enableDoubleTapZoom;
158
190
  [updatedPropNames addObject:@"enableDoubleTapZoom"];
159
191
  }
192
+ if (_annotations != RCTNSStringFromStringNilIfEmpty(newProps.annotations)) {
193
+ _annotations = RCTNSStringFromStringNilIfEmpty(newProps.annotations);
194
+ [updatedPropNames addObject:@"annotations"];
195
+ }
196
+ if (_annotationMode != newProps.annotationMode) {
197
+ _annotationMode = newProps.annotationMode;
198
+ [updatedPropNames addObject:@"annotationMode"];
199
+ }
200
+ if (_annotationTool != RCTNSStringFromStringNilIfEmpty(newProps.annotationTool)) {
201
+ _annotationTool = RCTNSStringFromStringNilIfEmpty(newProps.annotationTool);
202
+ [updatedPropNames addObject:@"annotationTool"];
203
+ }
204
+ if (_annotationEditable != newProps.annotationEditable) {
205
+ _annotationEditable = newProps.annotationEditable;
206
+ [updatedPropNames addObject:@"annotationEditable"];
207
+ }
208
+ if (_annotationIdMode != RCTNSStringFromStringNilIfEmpty(newProps.annotationIdMode)) {
209
+ _annotationIdMode = RCTNSStringFromStringNilIfEmpty(newProps.annotationIdMode);
210
+ [updatedPropNames addObject:@"annotationIdMode"];
211
+ }
160
212
  if (_fitPolicy != newProps.fitPolicy) {
161
213
  _fitPolicy = newProps.fitPolicy;
162
214
  [updatedPropNames addObject:@"fitPolicy"];
@@ -189,6 +241,13 @@ using namespace facebook::react;
189
241
 
190
242
  [super updateProps:props oldProps:oldProps];
191
243
  [self didSetProps:updatedPropNames];
244
+
245
+ if (_annotationOverlay) {
246
+ [_annotationOverlay replaceAnnotationsJSONString:_annotations editable:_annotationEditable idMode:_annotationIdMode];
247
+ [_annotationOverlay setAnnotationMode:_annotationMode tool:_annotationTool editable:_annotationEditable idMode:_annotationIdMode];
248
+ _annotationOverlay.pdfView = _pdfView;
249
+ _annotationOverlay.pdfDocument = _pdfDocument;
250
+ }
192
251
  }
193
252
 
194
253
  // already added in case https://github.com/facebook/react-native/pull/35378 has been merged
@@ -219,6 +278,10 @@ using namespace facebook::react;
219
278
  [self removeGestureRecognizer:_pinchRecognizer];
220
279
  [self removeGestureRecognizer:_longPressRecognizer];
221
280
  [self removeGestureRecognizer:_doubleTapEmptyRecognizer];
281
+ [self removeGestureRecognizer:_annotationPanRecognizer];
282
+
283
+ [_annotationOverlay removeFromSuperview];
284
+ _annotationOverlay = nil;
222
285
 
223
286
  [self initCommonProps];
224
287
  }
@@ -228,6 +291,7 @@ using namespace facebook::react;
228
291
  // Fabric equivalent of `reactSetFrame` method
229
292
  [super updateLayoutMetrics:layoutMetrics oldLayoutMetrics:oldLayoutMetrics];
230
293
  _pdfView.frame = CGRectMake(0, 0, layoutMetrics.frame.size.width, layoutMetrics.frame.size.height);
294
+ _annotationOverlay.frame = CGRectMake(0, 0, layoutMetrics.frame.size.width, layoutMetrics.frame.size.height);
231
295
 
232
296
  NSMutableArray *mProps = [_changedProps mutableCopy];
233
297
  if (_initialed) {
@@ -315,6 +379,12 @@ using namespace facebook::react;
315
379
  _autoScrollCurrentOffset = 0.0;
316
380
 
317
381
  [self addSubview:_pdfView];
382
+ _annotationOverlay = [[RNPDFAnnotationOverlay alloc] initWithFrame:self.bounds];
383
+ _annotationOverlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
384
+ _annotationOverlay.backgroundColor = UIColor.clearColor;
385
+ _annotationOverlay.userInteractionEnabled = YES;
386
+ [self addSubview:_annotationOverlay];
387
+ [self bringSubviewToFront:_annotationOverlay];
318
388
 
319
389
 
320
390
  // register notification
@@ -425,6 +495,10 @@ using namespace facebook::react;
425
495
  }
426
496
 
427
497
  _pdfView.document = _pdfDocument;
498
+ if (_annotationOverlay) {
499
+ _annotationOverlay.pdfView = _pdfView;
500
+ _annotationOverlay.pdfDocument = _pdfDocument;
501
+ }
428
502
  } else {
429
503
 
430
504
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"error|Load pdf failed. path=%s",_path.UTF8String]]];
@@ -766,6 +840,8 @@ using namespace facebook::react;
766
840
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"pageChanged|%lu|%lu", page+1, numberOfPages]]];
767
841
  }
768
842
 
843
+ [_annotationOverlay refreshDisplay];
844
+
769
845
  }
770
846
 
771
847
  - (void)onScaleChanged:(NSNotification *)noti
@@ -777,6 +853,8 @@ using namespace facebook::react;
777
853
  [self notifyOnChangeWithMessage:[[NSString alloc] initWithString:[NSString stringWithFormat:@"scaleChanged|%f", _scale]]];
778
854
  }
779
855
  }
856
+
857
+ [_annotationOverlay refreshDisplay];
780
858
  }
781
859
 
782
860
  #pragma mark gesture process
@@ -875,6 +953,35 @@ using namespace facebook::react;
875
953
  PDFPage *pdfPage = [_pdfView pageForPoint:point nearest:NO];
876
954
  if (pdfPage) {
877
955
  unsigned long page = [_pdfDocument indexForPage:pdfPage];
956
+ if (_annotationMode && _annotationEditable) {
957
+ if ([_annotationTool isEqualToString:@"select"]) {
958
+ [_annotationOverlay commitTextEditingIfNeeded];
959
+
960
+ NSDictionary *hit = [_annotationOverlay annotationSelectionHitAtPoint:point includeHandles:YES];
961
+ if (hit) {
962
+ NSDictionary *annotation = hit[@"annotation"];
963
+ NSString *hitPart = hit[@"hitPart"];
964
+ if ([hitPart isEqualToString:@"delete"]) {
965
+ [_annotationOverlay deleteAnnotation:annotation];
966
+ } else {
967
+ [_annotationOverlay selectAnnotation:annotation];
968
+ }
969
+
970
+ return;
971
+ }
972
+
973
+ [_annotationOverlay clearSelection];
974
+ [self notifyOnChangeWithMessage:
975
+ [[NSString alloc] initWithString:[NSString stringWithFormat:@"pageSingleTap|%lu|%f|%f", page+1, point.x, point.y]]];
976
+ return;
977
+ }
978
+
979
+ if ([_annotationTool isEqualToString:@"text"]) {
980
+ [_annotationOverlay createTextAnnotationAtViewPoint:point page:pdfPage];
981
+ return;
982
+ }
983
+ }
984
+
878
985
  [self notifyOnChangeWithMessage:
879
986
  [[NSString alloc] initWithString:[NSString stringWithFormat:@"pageSingleTap|%lu|%f|%f", page+1, point.x, point.y]]];
880
987
  }
@@ -904,6 +1011,77 @@ using namespace facebook::react;
904
1011
 
905
1012
  }
906
1013
 
1014
+ - (void)handleAnnotationPan:(UIPanGestureRecognizer *)sender
1015
+ {
1016
+ if (!_annotationMode || !_annotationEditable || _annotationTool == nil) {
1017
+ return;
1018
+ }
1019
+
1020
+ CGPoint point = [sender locationInView:self];
1021
+ PDFPage *pdfPage = [_pdfDocument pageAtIndex:MAX(0, [_pdfDocument indexForPage:[_pdfView pageForPoint:point nearest:NO]])];
1022
+ if (!pdfPage) {
1023
+ return;
1024
+ }
1025
+
1026
+ if ([_annotationTool isEqualToString:@"select"]) {
1027
+ if (sender.state == UIGestureRecognizerStateBegan) {
1028
+ [_annotationOverlay commitTextEditingIfNeeded];
1029
+
1030
+ NSDictionary *hit = [_annotationOverlay annotationSelectionHitAtPoint:point includeHandles:YES];
1031
+ if (!hit) {
1032
+ return;
1033
+ }
1034
+
1035
+ NSDictionary *annotation = hit[@"annotation"];
1036
+ NSString *hitPart = hit[@"hitPart"];
1037
+ if ([hitPart isEqualToString:@"delete"]) {
1038
+ [_annotationOverlay deleteAnnotation:annotation];
1039
+ return;
1040
+ }
1041
+
1042
+ [_annotationOverlay selectAnnotation:annotation];
1043
+ [_annotationOverlay beginSelectionInteractionAtPoint:point hit:hit];
1044
+ } else if (sender.state == UIGestureRecognizerStateChanged) {
1045
+ [_annotationOverlay updateSelectionInteractionAtPoint:point];
1046
+ } else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateFailed) {
1047
+ [_annotationOverlay endSelectionInteraction];
1048
+ }
1049
+ return;
1050
+ }
1051
+
1052
+ if (sender.state == UIGestureRecognizerStateBegan) {
1053
+ if ([_annotationTool isEqualToString:@"ink"]) {
1054
+ [_annotationOverlay beginInkAtViewPoint:point page:pdfPage];
1055
+ } else if ([@[@"highlight", @"underline", @"strikeout"] containsObject:_annotationTool]) {
1056
+ [_annotationOverlay beginMarkupAtViewPoint:point page:pdfPage type:_annotationTool];
1057
+ }
1058
+ } else if (sender.state == UIGestureRecognizerStateChanged) {
1059
+ if ([_annotationTool isEqualToString:@"ink"]) {
1060
+ [_annotationOverlay appendInkPointAtViewPoint:point page:pdfPage];
1061
+ } else if ([@[@"highlight", @"underline", @"strikeout"] containsObject:_annotationTool]) {
1062
+ [_annotationOverlay updateMarkupAtViewPoint:point page:pdfPage];
1063
+ }
1064
+ } else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateFailed) {
1065
+ if ([_annotationTool isEqualToString:@"ink"]) {
1066
+ [_annotationOverlay endInk];
1067
+ } else if ([@[@"highlight", @"underline", @"strikeout"] containsObject:_annotationTool]) {
1068
+ [_annotationOverlay endMarkup];
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ - (void)saveAnnotations
1074
+ {
1075
+ if (_annotationOverlay) {
1076
+ [_annotationOverlay commitTextEditingIfNeeded];
1077
+ NSString *jsonString = [_annotationOverlay serializedDocumentJSONStringWithEditable:_annotationEditable idMode:_annotationIdMode];
1078
+ [self notifyOnChangeWithMessage:[NSString stringWithFormat:@"annotationSaveComplete|%@", jsonString]];
1079
+ return;
1080
+ }
1081
+
1082
+ [self notifyOnChangeWithMessage:@"annotationSaveError|Annotation overlay unavailable"];
1083
+ }
1084
+
907
1085
  /**
908
1086
  * Bind tap
909
1087
  *
@@ -950,6 +1128,15 @@ using namespace facebook::react;
950
1128
  [self addGestureRecognizer:longPressRecognizer];
951
1129
  _longPressRecognizer = longPressRecognizer;
952
1130
 
1131
+ UIPanGestureRecognizer *annotationPanRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self
1132
+ action:@selector(handleAnnotationPan:)];
1133
+ annotationPanRecognizer.maximumNumberOfTouches = 1;
1134
+ annotationPanRecognizer.minimumNumberOfTouches = 1;
1135
+ annotationPanRecognizer.delegate = self;
1136
+ annotationPanRecognizer.cancelsTouchesInView = YES;
1137
+ [self addGestureRecognizer:annotationPanRecognizer];
1138
+ _annotationPanRecognizer = annotationPanRecognizer;
1139
+
953
1140
  // Override the _pdfView double tap gesture recognizer so that it doesn't confilict with custom double tap
954
1141
  UITapGestureRecognizer *doubleTapEmptyRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
955
1142
  action:@selector(handleDoubleTapEmpty:)];
@@ -961,11 +1148,28 @@ using namespace facebook::react;
961
1148
  - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
962
1149
 
963
1150
  {
1151
+ if (gestureRecognizer == _annotationPanRecognizer) {
1152
+ if (!_annotationMode || !_annotationEditable || _annotationTool == nil) {
1153
+ return NO;
1154
+ }
1155
+
1156
+ if ([_annotationTool isEqualToString:@"select"]) {
1157
+ CGPoint point = [gestureRecognizer locationInView:self];
1158
+ return [_annotationOverlay annotationSelectionHitAtPoint:point includeHandles:YES] != nil;
1159
+ }
1160
+
1161
+ return [@[@"ink", @"highlight", @"underline", @"strikeout"] containsObject:_annotationTool];
1162
+ }
1163
+
964
1164
  return !_singlePage;
965
1165
  }
966
1166
 
967
1167
  - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
968
1168
  {
1169
+ if (gestureRecognizer == _annotationPanRecognizer || otherGestureRecognizer == _annotationPanRecognizer) {
1170
+ return NO;
1171
+ }
1172
+
969
1173
  return !_singlePage;
970
1174
  }
971
1175
 
@@ -1137,6 +1341,998 @@ using namespace facebook::react;
1137
1341
  if ([_originalScrollDelegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
1138
1342
  [_originalScrollDelegate scrollViewDidScroll:scrollView];
1139
1343
  }
1344
+
1345
+ [_annotationOverlay refreshDisplay];
1346
+ }
1347
+
1348
+ @end
1349
+
1350
+ static UIColor *RNPDFColorFromHexString(NSString *hexString, UIColor *fallback)
1351
+ {
1352
+ if (![hexString isKindOfClass:[NSString class]] || hexString.length == 0) {
1353
+ return fallback;
1354
+ }
1355
+
1356
+ NSString *cleanHex = [[hexString stringByReplacingOccurrencesOfString:@"#" withString:@""] uppercaseString];
1357
+ unsigned int rgbValue = 0;
1358
+ if (cleanHex.length == 6) {
1359
+ NSScanner *scanner = [NSScanner scannerWithString:cleanHex];
1360
+ [scanner scanHexInt:&rgbValue];
1361
+ return [UIColor colorWithRed:((rgbValue & 0xFF0000) >> 16) / 255.0f
1362
+ green:((rgbValue & 0x00FF00) >> 8) / 255.0f
1363
+ blue:(rgbValue & 0x0000FF) / 255.0f
1364
+ alpha:1.0f];
1365
+ }
1366
+
1367
+ if (cleanHex.length == 8) {
1368
+ NSScanner *scanner = [NSScanner scannerWithString:cleanHex];
1369
+ [scanner scanHexInt:&rgbValue];
1370
+ return [UIColor colorWithRed:((rgbValue & 0x00FF0000) >> 16) / 255.0f
1371
+ green:((rgbValue & 0x0000FF00) >> 8) / 255.0f
1372
+ blue:(rgbValue & 0x000000FF) / 255.0f
1373
+ alpha:((rgbValue & 0xFF000000) >> 24) / 255.0f];
1374
+ }
1375
+
1376
+ return fallback;
1377
+ }
1378
+
1379
+ static NSString *RNPDFGenerateAnnotationId(void)
1380
+ {
1381
+ return [[NSUUID UUID] UUIDString];
1382
+ }
1383
+
1384
+ @implementation RNPDFAnnotationOverlay
1385
+ {
1386
+ NSMutableArray<NSMutableDictionary *> *_draftAnnotations;
1387
+ NSMutableDictionary *_activeInkAnnotation;
1388
+ NSMutableDictionary *_activeMarkupAnnotation;
1389
+ NSMutableDictionary *_activeTextAnnotation;
1390
+ NSString *_selectedAnnotationId;
1391
+ NSMutableDictionary *_activeSelectionAnnotation;
1392
+ NSString *_activeSelectionMode;
1393
+ NSString *_activeSelectionHandle;
1394
+ CGPoint _selectionStartPoint;
1395
+ CGRect _selectionStartBounds;
1396
+ NSArray *_selectionStartPoints;
1397
+ NSInteger _selectionPageIndex;
1398
+ CGPoint _activeMarkupStartNormalized;
1399
+ UITextView *_activeTextView;
1400
+ }
1401
+
1402
+ - (instancetype)initWithFrame:(CGRect)frame
1403
+ {
1404
+ if (self = [super initWithFrame:frame]) {
1405
+ _draftAnnotations = [NSMutableArray new];
1406
+ _annotationMode = NO;
1407
+ _annotationEditable = YES;
1408
+ _annotationTool = @"select";
1409
+ _annotationIdMode = @"auto";
1410
+ self.backgroundColor = UIColor.clearColor;
1411
+ self.opaque = NO;
1412
+ }
1413
+ return self;
1414
+ }
1415
+
1416
+ - (void)setPdfView:(PDFView *)pdfView
1417
+ {
1418
+ _pdfView = pdfView;
1419
+ [self refreshDisplay];
1420
+ }
1421
+
1422
+ - (void)setPdfDocument:(PDFDocument *)pdfDocument
1423
+ {
1424
+ _pdfDocument = pdfDocument;
1425
+ [self refreshDisplay];
1426
+ }
1427
+
1428
+ - (void)replaceAnnotationsJSONString:(NSString *)json editable:(BOOL)editable idMode:(NSString *)idMode
1429
+ {
1430
+ self.annotationEditable = editable;
1431
+ self.annotationIdMode = idMode ?: @"auto";
1432
+
1433
+ NSArray *parsedAnnotations = [self parseAnnotationsFromJSONString:json];
1434
+ _draftAnnotations = [parsedAnnotations mutableCopy] ?: [NSMutableArray new];
1435
+
1436
+ [self commitTextEditingIfNeeded];
1437
+ [self refreshDisplay];
1438
+ }
1439
+
1440
+ - (void)setAnnotationMode:(BOOL)annotationMode tool:(NSString *)tool editable:(BOOL)editable idMode:(NSString *)idMode
1441
+ {
1442
+ _annotationMode = annotationMode;
1443
+ _annotationEditable = editable;
1444
+ _annotationTool = tool ?: @"select";
1445
+ _annotationIdMode = idMode ?: @"auto";
1446
+
1447
+ if (!annotationMode) {
1448
+ [self commitTextEditingIfNeeded];
1449
+ }
1450
+ }
1451
+
1452
+ - (NSArray *)parseAnnotationsFromJSONString:(NSString *)json
1453
+ {
1454
+ if (![json isKindOfClass:[NSString class]] || json.length == 0) {
1455
+ return @[];
1456
+ }
1457
+
1458
+ NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
1459
+ if (!data) {
1460
+ return @[];
1461
+ }
1462
+
1463
+ NSError *error = nil;
1464
+ id parsed = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
1465
+ if (error || !parsed) {
1466
+ return @[];
1467
+ }
1468
+
1469
+ NSArray *items = nil;
1470
+ if ([parsed isKindOfClass:[NSDictionary class]]) {
1471
+ id candidate = parsed[@"annotations"];
1472
+ if ([candidate isKindOfClass:[NSArray class]]) {
1473
+ items = candidate;
1474
+ }
1475
+ } else if ([parsed isKindOfClass:[NSArray class]]) {
1476
+ items = parsed;
1477
+ }
1478
+
1479
+ if (![items isKindOfClass:[NSArray class]]) {
1480
+ return @[];
1481
+ }
1482
+
1483
+ NSMutableArray *normalized = [NSMutableArray arrayWithCapacity:items.count];
1484
+ for (id item in items) {
1485
+ if (![item isKindOfClass:[NSDictionary class]]) {
1486
+ continue;
1487
+ }
1488
+
1489
+ NSMutableDictionary *annotation = [item mutableCopy];
1490
+ if (!annotation[@"id"]) {
1491
+ annotation[@"id"] = RNPDFGenerateAnnotationId();
1492
+ }
1493
+ if (!annotation[@"page"]) {
1494
+ annotation[@"page"] = @(1);
1495
+ }
1496
+ [normalized addObject:annotation];
1497
+ }
1498
+
1499
+ return normalized;
1500
+ }
1501
+
1502
+ - (NSString *)serializedDocumentJSONStringWithEditable:(BOOL)editable idMode:(NSString *)idMode
1503
+ {
1504
+ NSMutableDictionary *document = [NSMutableDictionary dictionary];
1505
+ document[@"editable"] = @(editable);
1506
+ document[@"idMode"] = idMode ?: @"auto";
1507
+ document[@"annotations"] = [_draftAnnotations copy] ?: @[];
1508
+
1509
+ NSError *error = nil;
1510
+ NSData *data = [NSJSONSerialization dataWithJSONObject:document options:0 error:&error];
1511
+ if (error || !data) {
1512
+ return @"{}";
1513
+ }
1514
+
1515
+ return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] ?: @"{}";
1516
+ }
1517
+
1518
+ - (void)refreshDisplay
1519
+ {
1520
+ [self setNeedsDisplay];
1521
+ [self updateActiveTextEditorFrame];
1522
+ }
1523
+
1524
+ - (BOOL)hitTest:(CGPoint)point withEvent:(UIEvent *)event
1525
+ {
1526
+ if (_activeTextView) {
1527
+ if (CGRectContainsPoint(_activeTextView.frame, point)) {
1528
+ return _activeTextView;
1529
+ }
1530
+
1531
+ return self;
1532
+ }
1533
+
1534
+ return nil;
1535
+ }
1536
+
1537
+ - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
1538
+ {
1539
+ if (_activeTextView) {
1540
+ [self commitTextEditingIfNeeded];
1541
+ }
1542
+
1543
+ [super touchesBegan:touches withEvent:event];
1544
+ }
1545
+
1546
+ - (void)layoutSubviews
1547
+ {
1548
+ [super layoutSubviews];
1549
+ [self updateActiveTextEditorFrame];
1550
+ }
1551
+
1552
+ - (CGPoint)normalizedPointForViewPoint:(CGPoint)viewPoint page:(PDFPage *)page
1553
+ {
1554
+ if (!self.pdfView || !page) {
1555
+ return CGPointZero;
1556
+ }
1557
+
1558
+ CGPoint pagePoint = [self.pdfView convertPoint:viewPoint toPage:page];
1559
+ CGRect pageBounds = [page boundsForBox:kPDFDisplayBoxCropBox];
1560
+ if (page.rotation == 90 || page.rotation == 270) {
1561
+ pageBounds = CGRectMake(0, 0, pageBounds.size.height, pageBounds.size.width);
1562
+ }
1563
+
1564
+ CGFloat width = MAX(pageBounds.size.width, 1.0f);
1565
+ CGFloat height = MAX(pageBounds.size.height, 1.0f);
1566
+ return CGPointMake(pagePoint.x / width, 1.0f - (pagePoint.y / height));
1567
+ }
1568
+
1569
+ - (CGPoint)viewPointForNormalizedPoint:(CGPoint)normalizedPoint page:(PDFPage *)page
1570
+ {
1571
+ if (!self.pdfView || !page) {
1572
+ return CGPointZero;
1573
+ }
1574
+
1575
+ CGRect pageBounds = [page boundsForBox:kPDFDisplayBoxCropBox];
1576
+ if (page.rotation == 90 || page.rotation == 270) {
1577
+ pageBounds = CGRectMake(0, 0, pageBounds.size.height, pageBounds.size.width);
1578
+ }
1579
+
1580
+ CGFloat width = MAX(pageBounds.size.width, 1.0f);
1581
+ CGFloat height = MAX(pageBounds.size.height, 1.0f);
1582
+ CGPoint pagePoint = CGPointMake(normalizedPoint.x * width, (1.0f - normalizedPoint.y) * height);
1583
+ return [self.pdfView convertPoint:pagePoint fromPage:page];
1584
+ }
1585
+
1586
+ - (CGRect)viewRectForNormalizedBounds:(NSDictionary *)bounds page:(PDFPage *)page
1587
+ {
1588
+ if (!bounds || !page) {
1589
+ return CGRectZero;
1590
+ }
1591
+
1592
+ CGFloat x = [bounds[@"x"] doubleValue];
1593
+ CGFloat y = [bounds[@"y"] doubleValue];
1594
+ CGFloat width = [bounds[@"width"] doubleValue];
1595
+ CGFloat height = [bounds[@"height"] doubleValue];
1596
+
1597
+ CGPoint topLeft = [self viewPointForNormalizedPoint:CGPointMake(x, y) page:page];
1598
+ CGPoint topRight = [self viewPointForNormalizedPoint:CGPointMake(x + width, y) page:page];
1599
+ CGPoint bottomLeft = [self viewPointForNormalizedPoint:CGPointMake(x, y + height) page:page];
1600
+ CGPoint bottomRight = [self viewPointForNormalizedPoint:CGPointMake(x + width, y + height) page:page];
1601
+
1602
+ CGFloat minX = MIN(MIN(topLeft.x, topRight.x), MIN(bottomLeft.x, bottomRight.x));
1603
+ CGFloat maxX = MAX(MAX(topLeft.x, topRight.x), MAX(bottomLeft.x, bottomRight.x));
1604
+ CGFloat minY = MIN(MIN(topLeft.y, topRight.y), MIN(bottomLeft.y, bottomRight.y));
1605
+ CGFloat maxY = MAX(MAX(topLeft.y, topRight.y), MAX(bottomLeft.y, bottomRight.y));
1606
+
1607
+ return CGRectMake(minX, minY, MAX(maxX - minX, 1.0f), MAX(maxY - minY, 1.0f));
1608
+ }
1609
+
1610
+ - (CGRect)viewRectForAnnotation:(NSDictionary *)annotation page:(PDFPage *)page
1611
+ {
1612
+ if (!annotation || !page) {
1613
+ return CGRectZero;
1614
+ }
1615
+
1616
+ NSString *type = annotation[@"type"];
1617
+ if ([type isEqualToString:@"ink"]) {
1618
+ NSArray *points = annotation[@"points"];
1619
+ if (![points isKindOfClass:[NSArray class]] || points.count == 0) {
1620
+ return CGRectZero;
1621
+ }
1622
+
1623
+ CGFloat minX = CGFLOAT_MAX;
1624
+ CGFloat minY = CGFLOAT_MAX;
1625
+ CGFloat maxX = -CGFLOAT_MAX;
1626
+ CGFloat maxY = -CGFLOAT_MAX;
1627
+
1628
+ for (NSDictionary *point in points) {
1629
+ if (![point isKindOfClass:[NSDictionary class]]) {
1630
+ continue;
1631
+ }
1632
+
1633
+ CGPoint viewPoint = [self viewPointForNormalizedPoint:CGPointMake([point[@"x"] doubleValue], [point[@"y"] doubleValue]) page:page];
1634
+ minX = MIN(minX, viewPoint.x);
1635
+ minY = MIN(minY, viewPoint.y);
1636
+ maxX = MAX(maxX, viewPoint.x);
1637
+ maxY = MAX(maxY, viewPoint.y);
1638
+ }
1639
+
1640
+ if (minX == CGFLOAT_MAX || minY == CGFLOAT_MAX || maxX == -CGFLOAT_MAX || maxY == -CGFLOAT_MAX) {
1641
+ return CGRectZero;
1642
+ }
1643
+
1644
+ return CGRectMake(minX, minY, MAX(maxX - minX, 1.0f), MAX(maxY - minY, 1.0f));
1645
+ }
1646
+
1647
+ NSDictionary *bounds = annotation[@"bounds"];
1648
+ return [self viewRectForNormalizedBounds:bounds page:page];
1649
+ }
1650
+
1651
+ - (CGRect)normalizedBoundsForAnnotation:(NSDictionary *)annotation
1652
+ {
1653
+ if (!annotation) {
1654
+ return CGRectZero;
1655
+ }
1656
+
1657
+ NSString *type = annotation[@"type"];
1658
+ if ([type isEqualToString:@"ink"]) {
1659
+ NSArray *points = annotation[@"points"];
1660
+ if (![points isKindOfClass:[NSArray class]] || points.count == 0) {
1661
+ return CGRectZero;
1662
+ }
1663
+
1664
+ CGFloat minX = CGFLOAT_MAX;
1665
+ CGFloat minY = CGFLOAT_MAX;
1666
+ CGFloat maxX = -CGFLOAT_MAX;
1667
+ CGFloat maxY = -CGFLOAT_MAX;
1668
+
1669
+ for (NSDictionary *point in points) {
1670
+ if (![point isKindOfClass:[NSDictionary class]]) {
1671
+ continue;
1672
+ }
1673
+
1674
+ CGFloat x = [point[@"x"] doubleValue];
1675
+ CGFloat y = [point[@"y"] doubleValue];
1676
+ minX = MIN(minX, x);
1677
+ minY = MIN(minY, y);
1678
+ maxX = MAX(maxX, x);
1679
+ maxY = MAX(maxY, y);
1680
+ }
1681
+
1682
+ if (minX == CGFLOAT_MAX || minY == CGFLOAT_MAX || maxX == -CGFLOAT_MAX || maxY == -CGFLOAT_MAX) {
1683
+ return CGRectZero;
1684
+ }
1685
+
1686
+ return CGRectMake(minX, minY, MAX(maxX - minX, 0.001f), MAX(maxY - minY, 0.001f));
1687
+ }
1688
+
1689
+ NSDictionary *bounds = annotation[@"bounds"];
1690
+ if (![bounds isKindOfClass:[NSDictionary class]]) {
1691
+ return CGRectZero;
1692
+ }
1693
+
1694
+ CGFloat x = [bounds[@"x"] doubleValue];
1695
+ CGFloat y = [bounds[@"y"] doubleValue];
1696
+ CGFloat width = [bounds[@"width"] doubleValue];
1697
+ CGFloat height = [bounds[@"height"] doubleValue];
1698
+ return CGRectMake(x, y, MAX(width, 0.001f), MAX(height, 0.001f));
1699
+ }
1700
+
1701
+ - (NSArray *)copyPointsForAnnotation:(NSDictionary *)annotation
1702
+ {
1703
+ if (![annotation[@"type"] isEqualToString:@"ink"]) {
1704
+ return nil;
1705
+ }
1706
+
1707
+ NSArray *points = annotation[@"points"];
1708
+ if (![points isKindOfClass:[NSArray class]]) {
1709
+ return nil;
1710
+ }
1711
+
1712
+ return [points copy];
1713
+ }
1714
+
1715
+ - (NSDictionary *)selectedAnnotation
1716
+ {
1717
+ if (_selectedAnnotationId.length == 0) {
1718
+ return nil;
1719
+ }
1720
+
1721
+ for (NSDictionary *annotation in [_draftAnnotations reverseObjectEnumerator]) {
1722
+ if ([_selectedAnnotationId isEqualToString:annotation[@"id"]]) {
1723
+ return annotation;
1724
+ }
1725
+ }
1726
+
1727
+ return nil;
1728
+ }
1729
+
1730
+ - (CGRect)deleteHandleRectForRect:(CGRect)rect
1731
+ {
1732
+ CGFloat size = MAX(16.0f, MIN(rect.size.width, rect.size.height) * 0.18f);
1733
+ return CGRectMake(CGRectGetMaxX(rect) - size, CGRectGetMinY(rect) - size, size, size);
1734
+ }
1735
+
1736
+ - (CGRect)resizeHandleRectForRect:(CGRect)rect
1737
+ {
1738
+ CGFloat size = MAX(16.0f, MIN(rect.size.width, rect.size.height) * 0.18f);
1739
+ return CGRectMake(CGRectGetMaxX(rect) - size, CGRectGetMaxY(rect) - size, size, size);
1740
+ }
1741
+
1742
+ - (NSDictionary *)annotationSelectionHitAtPoint:(CGPoint)point includeHandles:(BOOL)includeHandles
1743
+ {
1744
+ if (!self.pdfDocument || _draftAnnotations.count == 0) {
1745
+ return nil;
1746
+ }
1747
+
1748
+ PDFPage *page = [self.pdfView pageForPoint:point nearest:NO];
1749
+ if (!page) {
1750
+ return nil;
1751
+ }
1752
+
1753
+ NSInteger pageIndex = [self.pdfDocument indexForPage:page];
1754
+ for (NSDictionary *annotation in [_draftAnnotations reverseObjectEnumerator]) {
1755
+ NSNumber *annotationPageNumber = annotation[@"page"];
1756
+ if (!annotationPageNumber || annotationPageNumber.integerValue - 1 != pageIndex) {
1757
+ continue;
1758
+ }
1759
+
1760
+ CGRect rect = [self viewRectForAnnotation:annotation page:page];
1761
+ if (CGRectIsEmpty(rect)) {
1762
+ continue;
1763
+ }
1764
+
1765
+ CGRect hitRect = CGRectInset(rect, -12.0f, -12.0f);
1766
+ if (!CGRectContainsPoint(hitRect, point)) {
1767
+ continue;
1768
+ }
1769
+
1770
+ NSString *hitPart = @"body";
1771
+ if (includeHandles && [_selectedAnnotationId isEqualToString:annotation[@"id"]]) {
1772
+ if (CGRectContainsPoint([self deleteHandleRectForRect:rect], point)) {
1773
+ hitPart = @"delete";
1774
+ } else if (CGRectContainsPoint([self resizeHandleRectForRect:rect], point)) {
1775
+ hitPart = @"resize";
1776
+ }
1777
+ }
1778
+
1779
+ return @{@"annotation": annotation,
1780
+ @"pageIndex": @(pageIndex),
1781
+ @"hitPart": hitPart,
1782
+ @"rect": [NSValue valueWithCGRect:rect]};
1783
+ }
1784
+
1785
+ return nil;
1786
+ }
1787
+
1788
+ - (void)selectAnnotation:(NSDictionary *)annotation
1789
+ {
1790
+ _selectedAnnotationId = [annotation[@"id"] copy];
1791
+ [self refreshDisplay];
1792
+ }
1793
+
1794
+ - (void)clearSelection
1795
+ {
1796
+ _selectedAnnotationId = nil;
1797
+ [self refreshDisplay];
1798
+ }
1799
+
1800
+ - (void)deleteAnnotation:(NSDictionary *)annotation
1801
+ {
1802
+ if (!annotation) {
1803
+ return;
1804
+ }
1805
+
1806
+ [self commitTextEditingIfNeeded];
1807
+
1808
+ NSString *annotationId = annotation[@"id"];
1809
+ if (annotationId.length == 0) {
1810
+ return;
1811
+ }
1812
+
1813
+ NSUInteger removeIndex = NSNotFound;
1814
+ for (NSUInteger index = 0; index < _draftAnnotations.count; index++) {
1815
+ NSDictionary *candidate = _draftAnnotations[index];
1816
+ if ([annotationId isEqualToString:candidate[@"id"]]) {
1817
+ removeIndex = index;
1818
+ break;
1819
+ }
1820
+ }
1821
+
1822
+ if (removeIndex != NSNotFound) {
1823
+ [_draftAnnotations removeObjectAtIndex:removeIndex];
1824
+ }
1825
+
1826
+ if ([_selectedAnnotationId isEqualToString:annotationId]) {
1827
+ _selectedAnnotationId = nil;
1828
+ }
1829
+
1830
+ [self endSelectionInteraction];
1831
+
1832
+ [self refreshDisplay];
1833
+ }
1834
+
1835
+ - (void)beginSelectionInteractionAtPoint:(CGPoint)point hit:(NSDictionary *)hit
1836
+ {
1837
+ NSDictionary *annotation = hit[@"annotation"];
1838
+ if (![annotation isKindOfClass:[NSDictionary class]]) {
1839
+ return;
1840
+ }
1841
+
1842
+ _activeSelectionAnnotation = (NSMutableDictionary *)annotation;
1843
+ _activeSelectionMode = [hit[@"hitPart"] isEqualToString:@"resize"] ? @"resize" : @"move";
1844
+ _activeSelectionHandle = hit[@"hitPart"] ?: @"body";
1845
+ _selectionStartPoint = point;
1846
+ _selectionPageIndex = [hit[@"pageIndex"] integerValue];
1847
+ _selectionStartBounds = [self normalizedBoundsForAnnotation:annotation];
1848
+ _selectionStartPoints = [self copyPointsForAnnotation:annotation];
1849
+ }
1850
+
1851
+ - (void)updateSelectionInteractionAtPoint:(CGPoint)point
1852
+ {
1853
+ if (!_activeSelectionAnnotation || _selectionPageIndex < 0 || !_pdfDocument || _selectionPageIndex >= _pdfDocument.pageCount) {
1854
+ return;
1855
+ }
1856
+
1857
+ PDFPage *page = [_pdfDocument pageAtIndex:_selectionPageIndex];
1858
+ if (!page) {
1859
+ return;
1860
+ }
1861
+
1862
+ CGPoint startNormalized = [self normalizedPointForViewPoint:_selectionStartPoint page:page];
1863
+ CGPoint currentNormalized = [self normalizedPointForViewPoint:point page:page];
1864
+ CGFloat deltaX = currentNormalized.x - startNormalized.x;
1865
+ CGFloat deltaY = currentNormalized.y - startNormalized.y;
1866
+
1867
+ CGRect newBounds = _selectionStartBounds;
1868
+ if ([_activeSelectionMode isEqualToString:@"resize"]) {
1869
+ newBounds.size.width = MAX(0.01f, _selectionStartBounds.size.width + deltaX);
1870
+ newBounds.size.height = MAX(0.01f, _selectionStartBounds.size.height + deltaY);
1871
+ } else {
1872
+ newBounds.origin.x = _selectionStartBounds.origin.x + deltaX;
1873
+ newBounds.origin.y = _selectionStartBounds.origin.y + deltaY;
1874
+ }
1875
+
1876
+ [self applySelectionBounds:newBounds toAnnotation:_activeSelectionAnnotation startBounds:_selectionStartBounds startPoints:_selectionStartPoints];
1877
+ [self refreshDisplay];
1878
+ }
1879
+
1880
+ - (void)endSelectionInteraction
1881
+ {
1882
+ _activeSelectionAnnotation = nil;
1883
+ _activeSelectionMode = nil;
1884
+ _activeSelectionHandle = nil;
1885
+ _selectionStartPoints = nil;
1886
+ _selectionPageIndex = -1;
1887
+ }
1888
+
1889
+ - (void)applySelectionBounds:(CGRect)newBounds toAnnotation:(NSMutableDictionary *)annotation startBounds:(CGRect)startBounds startPoints:(NSArray *)startPoints
1890
+ {
1891
+ if (!annotation) {
1892
+ return;
1893
+ }
1894
+
1895
+ NSString *type = annotation[@"type"];
1896
+ if ([type isEqualToString:@"ink"]) {
1897
+ if (![startPoints isKindOfClass:[NSArray class]]) {
1898
+ return;
1899
+ }
1900
+
1901
+ CGFloat startWidth = MAX(startBounds.size.width, 0.001f);
1902
+ CGFloat startHeight = MAX(startBounds.size.height, 0.001f);
1903
+ CGFloat newWidth = MAX(newBounds.size.width, 0.001f);
1904
+ CGFloat newHeight = MAX(newBounds.size.height, 0.001f);
1905
+
1906
+ NSMutableArray *transformedPoints = [NSMutableArray arrayWithCapacity:startPoints.count];
1907
+ for (NSDictionary *point in startPoints) {
1908
+ if (![point isKindOfClass:[NSDictionary class]]) {
1909
+ continue;
1910
+ }
1911
+
1912
+ CGFloat pointX = [point[@"x"] doubleValue];
1913
+ CGFloat pointY = [point[@"y"] doubleValue];
1914
+ CGFloat xRatio = (pointX - startBounds.origin.x) / startWidth;
1915
+ CGFloat yRatio = (pointY - startBounds.origin.y) / startHeight;
1916
+ CGFloat x = MIN(1.0f, MAX(0.0f, newBounds.origin.x + (xRatio * newWidth)));
1917
+ CGFloat y = MIN(1.0f, MAX(0.0f, newBounds.origin.y + (yRatio * newHeight)));
1918
+ [transformedPoints addObject:@{@"x": @(x), @"y": @(y)}];
1919
+ }
1920
+
1921
+ annotation[@"points"] = transformedPoints;
1922
+ return;
1923
+ }
1924
+
1925
+ CGFloat x = MIN(1.0f, MAX(0.0f, newBounds.origin.x));
1926
+ CGFloat y = MIN(1.0f, MAX(0.0f, newBounds.origin.y));
1927
+ CGFloat width = MIN(1.0f, MAX(0.01f, newBounds.size.width));
1928
+ CGFloat height = MIN(1.0f, MAX(0.01f, newBounds.size.height));
1929
+ annotation[@"bounds"] = @{@"x": @(x),
1930
+ @"y": @(y),
1931
+ @"width": @(width),
1932
+ @"height": @(height)};
1933
+ }
1934
+
1935
+ - (void)drawSelectionDecoration
1936
+ {
1937
+ if (!_annotationMode || !_annotationEditable || ![_annotationTool isEqualToString:@"select"]) {
1938
+ return;
1939
+ }
1940
+
1941
+ NSDictionary *annotation = [self selectedAnnotation];
1942
+ if (!annotation) {
1943
+ return;
1944
+ }
1945
+
1946
+ NSNumber *pageIndexValue = annotation[@"page"];
1947
+ NSInteger pageIndex = pageIndexValue.integerValue - 1;
1948
+ if (pageIndex < 0 || pageIndex >= _pdfDocument.pageCount) {
1949
+ return;
1950
+ }
1951
+
1952
+ PDFPage *page = [_pdfDocument pageAtIndex:pageIndex];
1953
+ CGRect rect = [self viewRectForAnnotation:annotation page:page];
1954
+ if (CGRectIsEmpty(rect)) {
1955
+ return;
1956
+ }
1957
+
1958
+ UIColor *outlineColor = [UIColor colorWithRed:0.13 green:0.27 blue:0.67 alpha:0.9];
1959
+ UIBezierPath *outlinePath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:4.0f];
1960
+ outlinePath.lineWidth = 2.0f;
1961
+ [outlineColor setStroke];
1962
+ [outlinePath stroke];
1963
+
1964
+ CGRect deleteHandle = [self deleteHandleRectForRect:rect];
1965
+ [[UIColor colorWithRed:0.83 green:0.19 blue:0.19 alpha:0.95] setFill];
1966
+ UIBezierPath *deletePath = [UIBezierPath bezierPathWithOvalInRect:deleteHandle];
1967
+ [deletePath fill];
1968
+
1969
+ CGRect resizeHandle = [self resizeHandleRectForRect:rect];
1970
+ [[UIColor colorWithRed:0.13 green:0.27 blue:0.67 alpha:0.95] setFill];
1971
+ UIBezierPath *resizePath = [UIBezierPath bezierPathWithRoundedRect:resizeHandle cornerRadius:2.0f];
1972
+ [resizePath fill];
1973
+ }
1974
+
1975
+ - (NSDictionary *)normalizedBoundsForViewRect:(CGRect)viewRect page:(PDFPage *)page
1976
+ {
1977
+ if (!self.pdfView || !page) {
1978
+ return @{@"x": @0, @"y": @0, @"width": @0, @"height": @0};
1979
+ }
1980
+
1981
+ CGPoint topLeft = [self normalizedPointForViewPoint:viewRect.origin page:page];
1982
+ CGPoint bottomRight = [self normalizedPointForViewPoint:CGPointMake(CGRectGetMaxX(viewRect), CGRectGetMaxY(viewRect)) page:page];
1983
+
1984
+ CGFloat minX = MIN(topLeft.x, bottomRight.x);
1985
+ CGFloat maxX = MAX(topLeft.x, bottomRight.x);
1986
+ CGFloat minY = MIN(topLeft.y, bottomRight.y);
1987
+ CGFloat maxY = MAX(topLeft.y, bottomRight.y);
1988
+
1989
+ return @{@"x": @(MAX(0, minX)),
1990
+ @"y": @(MAX(0, minY)),
1991
+ @"width": @(MAX(0, maxX - minX)),
1992
+ @"height": @(MAX(0, maxY - minY))};
1993
+ }
1994
+
1995
+ - (NSString *)nextLocalAnnotationId
1996
+ {
1997
+ return [NSString stringWithFormat:@"local-%@", RNPDFGenerateAnnotationId()];
1998
+ }
1999
+
2000
+ - (UIColor *)colorForAnnotationType:(NSString *)type style:(NSDictionary *)style
2001
+ {
2002
+ NSString *colorString = [style isKindOfClass:[NSDictionary class]] ? style[@"color"] : nil;
2003
+ if (colorString) {
2004
+ return RNPDFColorFromHexString(colorString, UIColor.blackColor);
2005
+ }
2006
+
2007
+ if ([type isEqualToString:@"highlight"]) {
2008
+ return [UIColor colorWithRed:1.0 green:0.93 blue:0.2 alpha:0.35];
2009
+ }
2010
+ if ([type isEqualToString:@"underline"]) {
2011
+ return [UIColor colorWithRed:0.2 green:0.45 blue:1.0 alpha:0.5];
2012
+ }
2013
+ if ([type isEqualToString:@"strikeout"]) {
2014
+ return [UIColor colorWithRed:1.0 green:0.2 blue:0.2 alpha:0.45];
2015
+ }
2016
+ if ([type isEqualToString:@"text"]) {
2017
+ return [UIColor colorWithRed:0.13 green:0.27 blue:0.67 alpha:1.0];
2018
+ }
2019
+
2020
+ return UIColor.blackColor;
2021
+ }
2022
+
2023
+ - (CGFloat)lineWidthForAnnotation:(NSDictionary *)annotation
2024
+ {
2025
+ NSDictionary *style = [annotation[@"style"] isKindOfClass:[NSDictionary class]] ? annotation[@"style"] : nil;
2026
+ NSNumber *thickness = style[@"thickness"];
2027
+ if ([thickness isKindOfClass:[NSNumber class]]) {
2028
+ return MAX(1.0f, thickness.floatValue);
2029
+ }
2030
+
2031
+ return 2.0f;
2032
+ }
2033
+
2034
+ - (UIFont *)fontForAnnotation:(NSDictionary *)annotation
2035
+ {
2036
+ NSDictionary *style = [annotation[@"style"] isKindOfClass:[NSDictionary class]] ? annotation[@"style"] : nil;
2037
+ NSNumber *fontSize = style[@"fontSize"];
2038
+ CGFloat size = [fontSize isKindOfClass:[NSNumber class]] ? MAX(10.0f, fontSize.floatValue) : 15.0f;
2039
+ return [UIFont systemFontOfSize:size];
2040
+ }
2041
+
2042
+ - (NSTextAlignment)alignmentForAnnotation:(NSDictionary *)annotation
2043
+ {
2044
+ NSDictionary *style = [annotation[@"style"] isKindOfClass:[NSDictionary class]] ? annotation[@"style"] : nil;
2045
+ NSString *alignment = style[@"textAlign"];
2046
+ if ([alignment isEqualToString:@"center"]) {
2047
+ return NSTextAlignmentCenter;
2048
+ }
2049
+ if ([alignment isEqualToString:@"right"]) {
2050
+ return NSTextAlignmentRight;
2051
+ }
2052
+
2053
+ return NSTextAlignmentLeft;
2054
+ }
2055
+
2056
+ - (void)drawRect:(CGRect)rect
2057
+ {
2058
+ [super drawRect:rect];
2059
+
2060
+ if (!_pdfDocument || _draftAnnotations.count == 0) {
2061
+ return;
2062
+ }
2063
+
2064
+ CGContextRef context = UIGraphicsGetCurrentContext();
2065
+ if (!context) {
2066
+ return;
2067
+ }
2068
+
2069
+ for (NSDictionary *annotation in _draftAnnotations) {
2070
+ NSNumber *pageIndexValue = annotation[@"page"];
2071
+ NSInteger pageIndex = pageIndexValue.integerValue - 1;
2072
+ if (pageIndex < 0 || pageIndex >= _pdfDocument.pageCount) {
2073
+ continue;
2074
+ }
2075
+
2076
+ PDFPage *page = [_pdfDocument pageAtIndex:pageIndex];
2077
+ NSString *type = annotation[@"type"];
2078
+ if ([type isEqualToString:@"ink"]) {
2079
+ NSArray *points = annotation[@"points"];
2080
+ if (points.count < 2) {
2081
+ continue;
2082
+ }
2083
+
2084
+ UIBezierPath *path = [UIBezierPath bezierPath];
2085
+ BOOL firstPoint = YES;
2086
+ for (NSDictionary *point in points) {
2087
+ CGPoint normalizedPoint = CGPointMake([point[@"x"] doubleValue], [point[@"y"] doubleValue]);
2088
+ CGPoint viewPoint = [self viewPointForNormalizedPoint:normalizedPoint page:page];
2089
+ if (firstPoint) {
2090
+ [path moveToPoint:viewPoint];
2091
+ firstPoint = NO;
2092
+ } else {
2093
+ [path addLineToPoint:viewPoint];
2094
+ }
2095
+ }
2096
+
2097
+ [[self colorForAnnotationType:type style:annotation[@"style"]] setStroke];
2098
+ path.lineWidth = [self lineWidthForAnnotation:annotation];
2099
+ path.lineJoinStyle = kCGLineJoinRound;
2100
+ path.lineCapStyle = kCGLineCapRound;
2101
+ [path stroke];
2102
+ } else if ([type isEqualToString:@"text"]) {
2103
+ NSDictionary *bounds = annotation[@"bounds"];
2104
+ CGRect viewRect = [self viewRectForNormalizedBounds:bounds page:page];
2105
+ if (CGRectIsEmpty(viewRect)) {
2106
+ continue;
2107
+ }
2108
+
2109
+ UIColor *borderColor = [self colorForAnnotationType:type style:annotation[@"style"]];
2110
+ [[UIColor colorWithWhite:1.0 alpha:0.78] setFill];
2111
+ UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:viewRect cornerRadius:4.0f];
2112
+ [path fill];
2113
+ [borderColor setStroke];
2114
+ path.lineWidth = 1.0f;
2115
+ [path stroke];
2116
+
2117
+ NSString *text = annotation[@"text"];
2118
+ if (![text isKindOfClass:[NSString class]]) {
2119
+ text = @"";
2120
+ }
2121
+
2122
+ NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
2123
+ paragraphStyle.alignment = [self alignmentForAnnotation:annotation];
2124
+ NSDictionary *attributes = @{
2125
+ NSFontAttributeName: [self fontForAnnotation:annotation],
2126
+ NSForegroundColorAttributeName: borderColor,
2127
+ NSParagraphStyleAttributeName: paragraphStyle,
2128
+ };
2129
+ [text drawInRect:CGRectInset(viewRect, 6.0f, 4.0f) withAttributes:attributes];
2130
+ } else if ([type isEqualToString:@"highlight"] || [type isEqualToString:@"underline"] || [type isEqualToString:@"strikeout"]) {
2131
+ NSDictionary *bounds = annotation[@"bounds"];
2132
+ CGRect viewRect = [self viewRectForNormalizedBounds:bounds page:page];
2133
+ if (CGRectIsEmpty(viewRect)) {
2134
+ continue;
2135
+ }
2136
+
2137
+ UIColor *fillColor = [self colorForAnnotationType:type style:annotation[@"style"]];
2138
+ CGContextSetFillColorWithColor(context, fillColor.CGColor);
2139
+ CGContextFillRect(context, viewRect);
2140
+
2141
+ if ([type isEqualToString:@"underline"] || [type isEqualToString:@"strikeout"]) {
2142
+ CGContextSetStrokeColorWithColor(context, fillColor.CGColor);
2143
+ CGContextSetLineWidth(context, MAX(1.0f, viewRect.size.height * 0.15f));
2144
+ CGFloat y = [type isEqualToString:@"underline"] ? CGRectGetMaxY(viewRect) - 2.0f : CGRectGetMidY(viewRect);
2145
+ CGContextMoveToPoint(context, CGRectGetMinX(viewRect), y);
2146
+ CGContextAddLineToPoint(context, CGRectGetMaxX(viewRect), y);
2147
+ CGContextStrokePath(context);
2148
+ }
2149
+ }
2150
+ }
2151
+
2152
+ [self drawSelectionDecoration];
2153
+ }
2154
+
2155
+ - (void)beginInkAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page
2156
+ {
2157
+ if (!self.annotationEditable || !page) {
2158
+ return;
2159
+ }
2160
+
2161
+ NSMutableDictionary *annotation = [@{
2162
+ @"id": [self nextLocalAnnotationId],
2163
+ @"page": @([_pdfDocument indexForPage:page] + 1),
2164
+ @"type": @"ink",
2165
+ @"points": [NSMutableArray array],
2166
+ @"style": @{@"color": @"#111111", @"thickness": @(2.0f)}
2167
+ } mutableCopy];
2168
+
2169
+ [_draftAnnotations addObject:annotation];
2170
+ _activeInkAnnotation = annotation;
2171
+ [self appendInkPointAtViewPoint:viewPoint page:page];
2172
+ }
2173
+
2174
+ - (void)appendInkPointAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page
2175
+ {
2176
+ if (!_activeInkAnnotation || !page) {
2177
+ return;
2178
+ }
2179
+
2180
+ CGPoint normalizedPoint = [self normalizedPointForViewPoint:viewPoint page:page];
2181
+ NSMutableArray *points = _activeInkAnnotation[@"points"];
2182
+ if (![points isKindOfClass:[NSMutableArray class]]) {
2183
+ points = [NSMutableArray array];
2184
+ _activeInkAnnotation[@"points"] = points;
2185
+ }
2186
+
2187
+ [points addObject:@{@"x": @(normalizedPoint.x), @"y": @(normalizedPoint.y)}];
2188
+ [self refreshDisplay];
2189
+ }
2190
+
2191
+ - (void)endInk
2192
+ {
2193
+ _activeInkAnnotation = nil;
2194
+ [self refreshDisplay];
2195
+ }
2196
+
2197
+ - (void)beginMarkupAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page type:(NSString *)type
2198
+ {
2199
+ if (!self.annotationEditable || !page) {
2200
+ return;
2201
+ }
2202
+
2203
+ CGPoint normalizedPoint = [self normalizedPointForViewPoint:viewPoint page:page];
2204
+ _activeMarkupStartNormalized = normalizedPoint;
2205
+
2206
+ NSMutableDictionary *annotation = [@{
2207
+ @"id": [self nextLocalAnnotationId],
2208
+ @"page": @([_pdfDocument indexForPage:page] + 1),
2209
+ @"type": type ?: @"highlight",
2210
+ @"bounds": @{@"x": @(normalizedPoint.x), @"y": @(normalizedPoint.y), @"width": @0, @"height": @0},
2211
+ @"style": @{}
2212
+ } mutableCopy];
2213
+
2214
+ [_draftAnnotations addObject:annotation];
2215
+ _activeMarkupAnnotation = annotation;
2216
+ [self refreshDisplay];
2217
+ }
2218
+
2219
+ - (void)updateMarkupAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page
2220
+ {
2221
+ if (!_activeMarkupAnnotation || !page) {
2222
+ return;
2223
+ }
2224
+
2225
+ CGPoint currentPoint = [self normalizedPointForViewPoint:viewPoint page:page];
2226
+ CGFloat minX = MIN(_activeMarkupStartNormalized.x, currentPoint.x);
2227
+ CGFloat minY = MIN(_activeMarkupStartNormalized.y, currentPoint.y);
2228
+ CGFloat maxX = MAX(_activeMarkupStartNormalized.x, currentPoint.x);
2229
+ CGFloat maxY = MAX(_activeMarkupStartNormalized.y, currentPoint.y);
2230
+
2231
+ _activeMarkupAnnotation[@"bounds"] = @{@"x": @(minX), @"y": @(minY), @"width": @(MAX(0, maxX - minX)), @"height": @(MAX(0, maxY - minY))};
2232
+ [self refreshDisplay];
2233
+ }
2234
+
2235
+ - (void)endMarkup
2236
+ {
2237
+ _activeMarkupAnnotation = nil;
2238
+ [self refreshDisplay];
2239
+ }
2240
+
2241
+ - (void)createTextAnnotationAtViewPoint:(CGPoint)viewPoint page:(PDFPage *)page
2242
+ {
2243
+ if (!self.annotationEditable || !page) {
2244
+ return;
2245
+ }
2246
+
2247
+ [self commitTextEditingIfNeeded];
2248
+
2249
+ CGPoint normalizedPoint = [self normalizedPointForViewPoint:viewPoint page:page];
2250
+ CGFloat width = 0.25f;
2251
+ CGFloat height = 0.12f;
2252
+ CGFloat maxX = MAX(0.0f, 1.0f - width);
2253
+ CGFloat maxY = MAX(0.0f, 1.0f - height);
2254
+ CGFloat x = MIN(MAX(normalizedPoint.x, 0.0f), maxX);
2255
+ CGFloat y = MIN(MAX(normalizedPoint.y, 0.0f), maxY);
2256
+ NSDictionary *bounds = @{@"x": @(x), @"y": @(y), @"width": @(width), @"height": @(height)};
2257
+
2258
+ NSMutableDictionary *annotation = [@{
2259
+ @"id": [self nextLocalAnnotationId],
2260
+ @"page": @([_pdfDocument indexForPage:page] + 1),
2261
+ @"type": @"text",
2262
+ @"bounds": bounds,
2263
+ @"text": @"",
2264
+ @"style": @{@"color": @"#2244aa", @"fontSize": @(15.0f), @"textAlign": @"left"}
2265
+ } mutableCopy];
2266
+
2267
+ [_draftAnnotations addObject:annotation];
2268
+ _activeTextAnnotation = annotation;
2269
+
2270
+ UITextView *textView = [[UITextView alloc] initWithFrame:[self viewRectForNormalizedBounds:bounds page:page]];
2271
+ textView.delegate = self;
2272
+ textView.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.9f];
2273
+ textView.layer.borderColor = [UIColor colorWithRed:0.2 green:0.4 blue:1.0 alpha:0.9].CGColor;
2274
+ textView.layer.borderWidth = 1.0f;
2275
+ textView.layer.cornerRadius = 4.0f;
2276
+ textView.clipsToBounds = YES;
2277
+ textView.textColor = [self colorForAnnotationType:@"text" style:annotation[@"style"]];
2278
+ textView.font = [self fontForAnnotation:annotation];
2279
+ textView.textAlignment = [self alignmentForAnnotation:annotation];
2280
+ textView.scrollEnabled = YES;
2281
+ textView.returnKeyType = UIReturnKeyDefault;
2282
+
2283
+ if (_activeTextView) {
2284
+ [_activeTextView removeFromSuperview];
2285
+ }
2286
+
2287
+ _activeTextView = textView;
2288
+ [self addSubview:_activeTextView];
2289
+ [_activeTextView becomeFirstResponder];
2290
+ [self refreshDisplay];
2291
+ }
2292
+
2293
+ - (void)updateActiveTextEditorFrame
2294
+ {
2295
+ if (!_activeTextView || !_activeTextAnnotation || !self.pdfDocument || !self.pdfView) {
2296
+ return;
2297
+ }
2298
+
2299
+ NSNumber *pageNumber = _activeTextAnnotation[@"page"];
2300
+ NSInteger pageIndex = pageNumber.integerValue - 1;
2301
+ if (pageIndex < 0 || pageIndex >= self.pdfDocument.pageCount) {
2302
+ return;
2303
+ }
2304
+
2305
+ PDFPage *page = [self.pdfDocument pageAtIndex:pageIndex];
2306
+ _activeTextView.frame = [self viewRectForNormalizedBounds:_activeTextAnnotation[@"bounds"] page:page];
2307
+ }
2308
+
2309
+ - (void)commitTextEditingIfNeeded
2310
+ {
2311
+ if (!_activeTextView || !_activeTextAnnotation) {
2312
+ return;
2313
+ }
2314
+
2315
+ _activeTextAnnotation[@"text"] = _activeTextView.text ?: @"";
2316
+ [_activeTextView resignFirstResponder];
2317
+ [_activeTextView removeFromSuperview];
2318
+ _activeTextView = nil;
2319
+ _activeTextAnnotation = nil;
2320
+ [self refreshDisplay];
2321
+ }
2322
+
2323
+ - (void)textViewDidChange:(UITextView *)textView
2324
+ {
2325
+ if (textView == _activeTextView && _activeTextAnnotation) {
2326
+ _activeTextAnnotation[@"text"] = textView.text ?: @"";
2327
+ [self setNeedsDisplay];
2328
+ }
2329
+ }
2330
+
2331
+ - (void)textViewDidEndEditing:(UITextView *)textView
2332
+ {
2333
+ if (textView == _activeTextView) {
2334
+ [self commitTextEditingIfNeeded];
2335
+ }
1140
2336
  }
1141
2337
 
1142
2338
  @end