react-native-rectangle-doc-scanner 3.66.0 → 3.67.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.
@@ -88,6 +88,18 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
88
88
  return Math.min(100, Math.max(0, quality));
89
89
  }, [quality]);
90
90
  const handlePictureTaken = (0, react_1.useCallback)((event) => {
91
+ const captureError = event?.error;
92
+ if (captureError) {
93
+ console.error('[DocScanner] Native capture error received:', captureError);
94
+ captureOriginRef.current = 'auto';
95
+ setIsAutoCapturing(false);
96
+ setDetectedRectangle(null);
97
+ if (captureResolvers.current) {
98
+ captureResolvers.current.reject(new Error(String(captureError)));
99
+ captureResolvers.current = null;
100
+ }
101
+ return;
102
+ }
91
103
  console.log('[DocScanner] handlePictureTaken called with event:', {
92
104
  hasInitialImage: !!event.initialImage,
93
105
  hasCroppedImage: !!event.croppedImage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.66.0",
3
+ "version": "3.67.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -150,6 +150,21 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
150
150
 
151
151
  const handlePictureTaken = useCallback(
152
152
  (event: PictureEvent) => {
153
+ const captureError = (event as any)?.error;
154
+ if (captureError) {
155
+ console.error('[DocScanner] Native capture error received:', captureError);
156
+ captureOriginRef.current = 'auto';
157
+ setIsAutoCapturing(false);
158
+ setDetectedRectangle(null);
159
+
160
+ if (captureResolvers.current) {
161
+ captureResolvers.current.reject(new Error(String(captureError)));
162
+ captureResolvers.current = null;
163
+ }
164
+
165
+ return;
166
+ }
167
+
153
168
  console.log('[DocScanner] handlePictureTaken called with event:', {
154
169
  hasInitialImage: !!event.initialImage,
155
170
  hasCroppedImage: !!event.croppedImage,
@@ -127,7 +127,28 @@
127
127
  - (void) capture {
128
128
  NSLog(@"[DocumentScanner] capture called");
129
129
  [self captureImageWithCompletionHander:^(UIImage *croppedImage, UIImage *initialImage, CIRectangleFeature *rectangleFeature) {
130
- NSLog(@"[DocumentScanner] captureImageWithCompletionHander callback - croppedImage: %@, initialImage: %@", croppedImage ? @"YES" : @"NO", initialImage ? @"YES" : @"NO");
130
+ NSLog(@"[DocumentScanner] captureImageWithCompletionHander callback - croppedImage: %@, initialImage: %@", croppedImage ? @"YES" : @"NO", initialImage ? @"YES" : @"NO");
131
+
132
+ if (!croppedImage && initialImage) {
133
+ // Use initial image when cropping is not available
134
+ croppedImage = initialImage;
135
+ } else if (!initialImage && croppedImage) {
136
+ // Mirror cropped image so downstream logic continues to work
137
+ initialImage = croppedImage;
138
+ }
139
+
140
+ if (!croppedImage || !initialImage) {
141
+ NSLog(@"[DocumentScanner] capture failed - missing image data");
142
+ if (self.onPictureTaken) {
143
+ self.onPictureTaken(@{ @"error": @"capture_failed" });
144
+ }
145
+
146
+ if (!self.captureMultiple) {
147
+ [self stop];
148
+ }
149
+ return;
150
+ }
151
+
131
152
  if (self.onPictureTaken) {
132
153
  NSLog(@"[DocumentScanner] Calling onPictureTaken");
133
154
  // Use maximum JPEG quality (1.0) or user's quality setting, whichever is higher
@@ -15,6 +15,19 @@
15
15
  #import <ImageIO/ImageIO.h>
16
16
  #import <GLKit/GLKit.h>
17
17
 
18
+ static inline void dispatch_async_main_queue(dispatch_block_t block)
19
+ {
20
+ if (!block) {
21
+ return;
22
+ }
23
+
24
+ if ([NSThread isMainThread]) {
25
+ block();
26
+ } else {
27
+ dispatch_async(dispatch_get_main_queue(), block);
28
+ }
29
+ }
30
+
18
31
  @interface IPDFCameraViewController () <AVCaptureVideoDataOutputSampleBufferDelegate, AVCapturePhotoCaptureDelegate>
19
32
 
20
33
  @property (nonatomic,strong) AVCaptureSession *captureSession;
@@ -47,6 +60,39 @@
47
60
  BOOL _isCapturing;
48
61
  }
49
62
 
63
+ - (void)completeCaptureWithCroppedImage:(UIImage *)croppedImage
64
+ initialImage:(UIImage *)initialImage
65
+ rectangle:(CIRectangleFeature *)rectangleFeature
66
+ error:(NSError *)error
67
+ {
68
+ void (^completionHandler)(UIImage *, UIImage *, CIRectangleFeature *) = self.captureCompletionHandler;
69
+
70
+ dispatch_async_main_queue(^{
71
+ if (error) {
72
+ NSLog(@"[IPDFCameraViewController] Completing capture with error: %@", error.localizedDescription);
73
+ if (completionHandler) {
74
+ completionHandler(nil, nil, nil);
75
+ }
76
+ } else {
77
+ UIImage *resolvedInitial = initialImage ?: croppedImage;
78
+ UIImage *resolvedCropped = croppedImage ?: resolvedInitial;
79
+
80
+ if (!resolvedInitial || !resolvedCropped) {
81
+ NSLog(@"[IPDFCameraViewController] Missing images during completion, sending failure to JS");
82
+ if (completionHandler) {
83
+ completionHandler(nil, nil, nil);
84
+ }
85
+ } else if (completionHandler) {
86
+ completionHandler(resolvedCropped, resolvedInitial, rectangleFeature);
87
+ }
88
+ }
89
+
90
+ self.captureCompletionHandler = nil;
91
+ self->_isCapturing = NO;
92
+ [self hideGLKView:NO completion:nil];
93
+ });
94
+ }
95
+
50
96
  - (void)awakeFromNib
51
97
  {
52
98
  [super awakeFromNib];
@@ -452,7 +498,10 @@
452
498
 
453
499
  if (!self.captureSession || !self.captureSession.isRunning) {
454
500
  NSLog(@"[IPDFCameraViewController] ERROR: captureSession is not running");
455
- _isCapturing = NO;
501
+ NSError *error = [NSError errorWithDomain:@"IPDFCameraViewController"
502
+ code:-200
503
+ userInfo:@{ NSLocalizedDescriptionKey: @"capture_session_not_running" }];
504
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
456
505
  return;
457
506
  }
458
507
 
@@ -488,9 +537,10 @@
488
537
  {
489
538
  if (!self.stillImageOutput) {
490
539
  NSLog(@"[IPDFCameraViewController] ERROR: stillImageOutput is nil");
491
- _isCapturing = NO;
492
- self.captureCompletionHandler = nil;
493
- [weakSelf hideGLKView:NO completion:nil];
540
+ NSError *error = [NSError errorWithDomain:@"IPDFCameraViewController"
541
+ code:-201
542
+ userInfo:@{ NSLocalizedDescriptionKey: @"missing_still_image_output" }];
543
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
494
544
  return;
495
545
  }
496
546
 
@@ -510,9 +560,10 @@
510
560
 
511
561
  if (!videoConnection) {
512
562
  NSLog(@"[IPDFCameraViewController] ERROR: No video connection found");
513
- _isCapturing = NO;
514
- self.captureCompletionHandler = nil;
515
- [weakSelf hideGLKView:NO completion:nil];
563
+ NSError *error = [NSError errorWithDomain:@"IPDFCameraViewController"
564
+ code:-202
565
+ userInfo:@{ NSLocalizedDescriptionKey: @"no_video_connection" }];
566
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
516
567
  return;
517
568
  }
518
569
 
@@ -530,9 +581,7 @@
530
581
 
531
582
  if (error) {
532
583
  NSLog(@"[IPDFCameraViewController] ERROR in didFinishProcessingPhoto: %@", error);
533
- _isCapturing = NO;
534
- self.captureCompletionHandler = nil;
535
- [self hideGLKView:NO completion:nil];
584
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
536
585
  return;
537
586
  }
538
587
 
@@ -540,9 +589,10 @@
540
589
  NSData *imageData = [photo fileDataRepresentation];
541
590
  if (!imageData) {
542
591
  NSLog(@"[IPDFCameraViewController] ERROR: Failed to get image data from photo");
543
- _isCapturing = NO;
544
- self.captureCompletionHandler = nil;
545
- [self hideGLKView:NO completion:nil];
592
+ NSError *dataError = [NSError errorWithDomain:@"IPDFCameraViewController"
593
+ code:-203
594
+ userInfo:@{ NSLocalizedDescriptionKey: @"no_image_data_from_photo" }];
595
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:dataError];
546
596
  return;
547
597
  }
548
598
 
@@ -556,17 +606,16 @@
556
606
 
557
607
  if (error) {
558
608
  NSLog(@"[IPDFCameraViewController] ERROR in didFinishProcessingPhotoSampleBuffer: %@", error);
559
- _isCapturing = NO;
560
- self.captureCompletionHandler = nil;
561
- [self hideGLKView:NO completion:nil];
609
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
562
610
  return;
563
611
  }
564
612
 
565
613
  if (!photoSampleBuffer) {
566
614
  NSLog(@"[IPDFCameraViewController] ERROR: photoSampleBuffer is nil");
567
- _isCapturing = NO;
568
- self.captureCompletionHandler = nil;
569
- [self hideGLKView:NO completion:nil];
615
+ NSError *bufferError = [NSError errorWithDomain:@"IPDFCameraViewController"
616
+ code:-204
617
+ userInfo:@{ NSLocalizedDescriptionKey: @"photo_sample_buffer_nil" }];
618
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:bufferError];
570
619
  return;
571
620
  }
572
621
 
@@ -575,9 +624,10 @@
575
624
 
576
625
  if (!imageData) {
577
626
  NSLog(@"[IPDFCameraViewController] ERROR: Failed to create JPEG data from photo sample buffer");
578
- _isCapturing = NO;
579
- self.captureCompletionHandler = nil;
580
- [self hideGLKView:NO completion:nil];
627
+ NSError *dataError = [NSError errorWithDomain:@"IPDFCameraViewController"
628
+ code:-205
629
+ userInfo:@{ NSLocalizedDescriptionKey: @"jpeg_conversion_failed" }];
630
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:dataError];
581
631
  return;
582
632
  }
583
633
 
@@ -591,17 +641,16 @@
591
641
 
592
642
  if (error) {
593
643
  NSLog(@"[IPDFCameraViewController] ERROR capturing image: %@", error);
594
- _isCapturing = NO;
595
- self.captureCompletionHandler = nil;
596
- [self hideGLKView:NO completion:nil];
644
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
597
645
  return;
598
646
  }
599
647
 
600
648
  if (!sampleBuffer) {
601
649
  NSLog(@"[IPDFCameraViewController] ERROR: sampleBuffer is nil");
602
- _isCapturing = NO;
603
- self.captureCompletionHandler = nil;
604
- [self hideGLKView:NO completion:nil];
650
+ NSError *bufferError = [NSError errorWithDomain:@"IPDFCameraViewController"
651
+ code:-206
652
+ userInfo:@{ NSLocalizedDescriptionKey: @"sample_buffer_nil" }];
653
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:bufferError];
605
654
  return;
606
655
  }
607
656
 
@@ -610,9 +659,10 @@
610
659
 
611
660
  if (!imageData) {
612
661
  NSLog(@"[IPDFCameraViewController] ERROR: Failed to create image data from sample buffer (legacy)");
613
- _isCapturing = NO;
614
- self.captureCompletionHandler = nil;
615
- [self hideGLKView:NO completion:nil];
662
+ NSError *dataError = [NSError errorWithDomain:@"IPDFCameraViewController"
663
+ code:-207
664
+ userInfo:@{ NSLocalizedDescriptionKey: @"legacy_sample_conversion_failed" }];
665
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:dataError];
616
666
  return;
617
667
  }
618
668
 
@@ -623,67 +673,63 @@
623
673
  - (void)processImageData:(NSData *)imageData {
624
674
  NSLog(@"[IPDFCameraViewController] processImageData called, imageData size: %lu bytes", (unsigned long)imageData.length);
625
675
 
626
- __weak typeof(self) weakSelf = self;
627
- void (^completionHandler)(UIImage *, UIImage *, CIRectangleFeature *) = self.captureCompletionHandler;
628
-
629
- if (!completionHandler) {
630
- NSLog(@"[IPDFCameraViewController] ERROR: completionHandler is nil");
631
- _isCapturing = NO;
632
- [self hideGLKView:NO completion:nil];
676
+ if (!imageData || imageData.length == 0) {
677
+ NSError *dataError = [NSError errorWithDomain:@"IPDFCameraViewController"
678
+ code:-208
679
+ userInfo:@{ NSLocalizedDescriptionKey: @"empty_image_data" }];
680
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:dataError];
633
681
  return;
634
682
  }
635
683
 
636
- if (self.cameraViewType == IPDFCameraViewTypeBlackAndWhite || self.isBorderDetectionEnabled)
637
- {
638
- CIImage *enhancedImage = [CIImage imageWithData:imageData];
639
-
640
- if (self.cameraViewType == IPDFCameraViewTypeBlackAndWhite)
641
- {
642
- enhancedImage = [self filteredImageUsingEnhanceFilterOnImage:enhancedImage];
643
- }
644
- else
645
- {
646
- enhancedImage = [self filteredImageUsingContrastFilterOnImage:enhancedImage];
647
- }
648
-
649
- if (self.isBorderDetectionEnabled && rectangleDetectionConfidenceHighEnough(_imageDedectionConfidence))
650
- {
651
- CIRectangleFeature *rectangleFeature = [self biggestRectangleInRectangles:[[self highAccuracyRectangleDetector] featuresInImage:enhancedImage]];
652
-
653
- if (rectangleFeature)
654
- {
655
- enhancedImage = [self correctPerspectiveForImage:enhancedImage withFeatures:rectangleFeature];
684
+ UIImage *initialImage = [UIImage imageWithData:imageData];
685
+ if (!initialImage) {
686
+ NSError *conversionError = [NSError errorWithDomain:@"IPDFCameraViewController"
687
+ code:-209
688
+ userInfo:@{ NSLocalizedDescriptionKey: @"initial_image_conversion_failed" }];
689
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:conversionError];
690
+ return;
691
+ }
656
692
 
657
- UIGraphicsBeginImageContext(CGSizeMake(enhancedImage.extent.size.height, enhancedImage.extent.size.width));
658
- [[UIImage imageWithCIImage:enhancedImage scale:1.0 orientation:UIImageOrientationRight] drawInRect:CGRectMake(0,0, enhancedImage.extent.size.height, enhancedImage.extent.size.width)];
659
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
660
- UIImage *initialImage = [UIImage imageWithData:imageData];
661
- UIGraphicsEndImageContext();
693
+ UIImage *croppedImage = initialImage;
694
+ CIRectangleFeature *rectangleFeature = nil;
662
695
 
663
- [weakSelf hideGLKView:NO completion:nil];
664
- completionHandler(image, initialImage, rectangleFeature);
696
+ BOOL shouldEnhance = (self.cameraViewType == IPDFCameraViewTypeBlackAndWhite) || self.isBorderDetectionEnabled;
697
+ if (shouldEnhance) {
698
+ CIImage *processedImage = [CIImage imageWithData:imageData];
699
+ if (!processedImage) {
700
+ NSLog(@"[IPDFCameraViewController] Unable to create CIImage from data, returning original image");
701
+ } else {
702
+ if (self.cameraViewType == IPDFCameraViewTypeBlackAndWhite) {
703
+ processedImage = [self filteredImageUsingEnhanceFilterOnImage:processedImage];
665
704
  } else {
666
- // No rectangle detected, return original image
667
- NSLog(@"[IPDFCameraViewController] No rectangle detected during manual capture, returning original image");
668
- [weakSelf hideGLKView:NO completion:nil];
669
- UIImage *initialImage = [UIImage imageWithData:imageData];
670
- completionHandler(initialImage, initialImage, nil);
705
+ processedImage = [self filteredImageUsingContrastFilterOnImage:processedImage];
706
+ }
707
+
708
+ if (self.isBorderDetectionEnabled && rectangleDetectionConfidenceHighEnough(_imageDedectionConfidence)) {
709
+ CIRectangleFeature *detectedRectangle = [self biggestRectangleInRectangles:[[self highAccuracyRectangleDetector] featuresInImage:processedImage]];
710
+
711
+ if (detectedRectangle) {
712
+ rectangleFeature = detectedRectangle;
713
+ CIImage *correctedImage = [self correctPerspectiveForImage:processedImage withFeatures:detectedRectangle];
714
+
715
+ UIGraphicsBeginImageContext(CGSizeMake(correctedImage.extent.size.height, correctedImage.extent.size.width));
716
+ [[UIImage imageWithCIImage:correctedImage scale:1.0 orientation:UIImageOrientationRight] drawInRect:CGRectMake(0, 0, correctedImage.extent.size.height, correctedImage.extent.size.width)];
717
+ UIImage *perspectiveCorrectedImage = UIGraphicsGetImageFromCurrentImageContext();
718
+ UIGraphicsEndImageContext();
719
+
720
+ if (perspectiveCorrectedImage) {
721
+ croppedImage = perspectiveCorrectedImage;
722
+ } else {
723
+ NSLog(@"[IPDFCameraViewController] Failed to create perspective corrected image, using original");
724
+ }
725
+ } else {
726
+ NSLog(@"[IPDFCameraViewController] No rectangle detected during manual capture, returning original image");
727
+ }
671
728
  }
672
- } else {
673
- [weakSelf hideGLKView:NO completion:nil];
674
- UIImage *initialImage = [UIImage imageWithData:imageData];
675
- completionHandler(initialImage, initialImage, nil);
676
729
  }
677
730
  }
678
- else
679
- {
680
- [weakSelf hideGLKView:NO completion:nil];
681
- UIImage *initialImage = [UIImage imageWithData:imageData];
682
- completionHandler(initialImage, initialImage, nil);
683
- }
684
731
 
685
- _isCapturing = NO;
686
- self.captureCompletionHandler = nil;
732
+ [self completeCaptureWithCroppedImage:croppedImage initialImage:initialImage rectangle:rectangleFeature error:nil];
687
733
  }
688
734
 
689
735
  - (void)hideGLKView:(BOOL)hidden completion:(void(^)())completion
@@ -35,30 +35,35 @@ RCT_EXPORT_VIEW_PROPERTY(brightness, float)
35
35
  RCT_EXPORT_VIEW_PROPERTY(contrast, float)
36
36
 
37
37
  // Main capture method - uses the last created scanner view
38
- RCT_EXPORT_METHOD(capture) {
39
- NSLog(@"[RNPdfScannerManager] capture called, scannerView: %@", _scannerView ? @"YES" : @"NO");
38
+ RCT_EXPORT_METHOD(capture:(nullable NSNumber *)reactTag) {
39
+ NSLog(@"[RNPdfScannerManager] capture called with reactTag: %@", reactTag);
40
40
  dispatch_async(dispatch_get_main_queue(), ^{
41
- if (!self->_scannerView) {
42
- NSLog(@"[RNPdfScannerManager] ERROR: No scanner view available");
43
- return;
41
+ DocumentScannerView *targetView = nil;
42
+
43
+ if (reactTag) {
44
+ UIView *view = [self.bridge.uiManager viewForReactTag:reactTag];
45
+ if ([view isKindOfClass:[DocumentScannerView class]]) {
46
+ targetView = (DocumentScannerView *)view;
47
+ self->_scannerView = targetView;
48
+ } else if (view) {
49
+ NSLog(@"[RNPdfScannerManager] View for tag %@ is not DocumentScannerView: %@", reactTag, NSStringFromClass(view.class));
50
+ } else {
51
+ NSLog(@"[RNPdfScannerManager] No view found for tag %@", reactTag);
52
+ }
44
53
  }
45
- NSLog(@"[RNPdfScannerManager] Calling capture on view: %@", self->_scannerView);
46
- [self->_scannerView capture];
47
- });
48
- }
49
54
 
50
- // Alternative method that takes reactTag - for future use
51
- RCT_EXPORT_METHOD(captureWithTag:(nonnull NSNumber *)reactTag) {
52
- NSLog(@"[RNPdfScannerManager] captureWithTag called with reactTag: %@", reactTag);
53
- dispatch_async(dispatch_get_main_queue(), ^{
54
- UIView *view = [self.bridge.uiManager viewForReactTag:reactTag];
55
- if (!view || ![view isKindOfClass:[DocumentScannerView class]]) {
56
- NSLog(@"[RNPdfScannerManager] Cannot find DocumentScannerView with tag #%@", reactTag);
55
+ if (!targetView && self->_scannerView) {
56
+ NSLog(@"[RNPdfScannerManager] Falling back to last known scanner view");
57
+ targetView = self->_scannerView;
58
+ }
59
+
60
+ if (!targetView) {
61
+ NSLog(@"[RNPdfScannerManager] ERROR: No scanner view available for capture");
57
62
  return;
58
63
  }
59
- DocumentScannerView *scannerView = (DocumentScannerView *)view;
60
- NSLog(@"[RNPdfScannerManager] Calling capture on view: %@", scannerView);
61
- [scannerView capture];
64
+
65
+ NSLog(@"[RNPdfScannerManager] Calling capture on view: %@", targetView);
66
+ [targetView capture];
62
67
  });
63
68
  }
64
69