react-native-rectangle-doc-scanner 3.67.0 → 3.68.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.
@@ -168,46 +168,49 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
168
168
  captureOriginRef.current = 'auto';
169
169
  return Promise.reject(new Error('capture_in_progress'));
170
170
  }
171
- console.log('[DocScanner] Calling native capture method...');
172
- let result;
171
+ console.log('[DocScanner] Calling native capture method (now returns Promise)...');
173
172
  try {
174
- result = instance.capture();
173
+ const result = instance.capture();
175
174
  console.log('[DocScanner] Native capture method called, result type:', typeof result, 'isPromise:', !!(result && typeof result.then === 'function'));
175
+ if (result && typeof result.then === 'function') {
176
+ console.log('[DocScanner] Native returned a promise, waiting for resolution...');
177
+ return result
178
+ .then((payload) => {
179
+ console.log('[DocScanner] Native promise resolved with payload:', {
180
+ hasCroppedImage: !!payload.croppedImage,
181
+ hasInitialImage: !!payload.initialImage,
182
+ });
183
+ handlePictureTaken(payload);
184
+ return payload;
185
+ })
186
+ .catch((error) => {
187
+ console.error('[DocScanner] Native promise rejected:', error);
188
+ captureOriginRef.current = 'auto';
189
+ throw error;
190
+ });
191
+ }
192
+ // Fallback for legacy event-based approach
193
+ console.warn('[DocScanner] Native did not return a promise, using callback-based approach (legacy)');
194
+ return new Promise((resolve, reject) => {
195
+ captureResolvers.current = {
196
+ resolve: (value) => {
197
+ console.log('[DocScanner] Callback resolver called with:', value);
198
+ captureOriginRef.current = 'auto';
199
+ resolve(value);
200
+ },
201
+ reject: (reason) => {
202
+ console.error('[DocScanner] Callback rejector called with:', reason);
203
+ captureOriginRef.current = 'auto';
204
+ reject(reason);
205
+ },
206
+ };
207
+ });
176
208
  }
177
209
  catch (error) {
178
210
  console.error('[DocScanner] Native capture threw error:', error);
179
211
  captureOriginRef.current = 'auto';
180
212
  return Promise.reject(error);
181
213
  }
182
- if (result && typeof result.then === 'function') {
183
- console.log('[DocScanner] Native returned a promise, waiting for resolution...');
184
- return result
185
- .then((payload) => {
186
- console.log('[DocScanner] Native promise resolved with payload:', payload);
187
- handlePictureTaken(payload);
188
- return payload;
189
- })
190
- .catch((error) => {
191
- console.error('[DocScanner] Native promise rejected:', error);
192
- captureOriginRef.current = 'auto';
193
- throw error;
194
- });
195
- }
196
- console.log('[DocScanner] Native did not return a promise, using callback-based approach');
197
- return new Promise((resolve, reject) => {
198
- captureResolvers.current = {
199
- resolve: (value) => {
200
- console.log('[DocScanner] Callback resolver called with:', value);
201
- captureOriginRef.current = 'auto';
202
- resolve(value);
203
- },
204
- reject: (reason) => {
205
- console.error('[DocScanner] Callback rejector called with:', reason);
206
- captureOriginRef.current = 'auto';
207
- reject(reason);
208
- },
209
- };
210
- });
211
214
  }, [handlePictureTaken]);
212
215
  const handleManualCapture = (0, react_1.useCallback)(() => {
213
216
  captureOriginRef.current = 'manual';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.67.0",
3
+ "version": "3.68.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -247,47 +247,50 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(
247
247
  return Promise.reject(new Error('capture_in_progress'));
248
248
  }
249
249
 
250
- console.log('[DocScanner] Calling native capture method...');
251
- let result: any;
250
+ console.log('[DocScanner] Calling native capture method (now returns Promise)...');
252
251
  try {
253
- result = instance.capture();
252
+ const result = instance.capture();
254
253
  console.log('[DocScanner] Native capture method called, result type:', typeof result, 'isPromise:', !!(result && typeof result.then === 'function'));
254
+
255
+ if (result && typeof result.then === 'function') {
256
+ console.log('[DocScanner] Native returned a promise, waiting for resolution...');
257
+ return result
258
+ .then((payload: PictureEvent) => {
259
+ console.log('[DocScanner] Native promise resolved with payload:', {
260
+ hasCroppedImage: !!payload.croppedImage,
261
+ hasInitialImage: !!payload.initialImage,
262
+ });
263
+ handlePictureTaken(payload);
264
+ return payload;
265
+ })
266
+ .catch((error: unknown) => {
267
+ console.error('[DocScanner] Native promise rejected:', error);
268
+ captureOriginRef.current = 'auto';
269
+ throw error;
270
+ });
271
+ }
272
+
273
+ // Fallback for legacy event-based approach
274
+ console.warn('[DocScanner] Native did not return a promise, using callback-based approach (legacy)');
275
+ return new Promise<PictureEvent>((resolve, reject) => {
276
+ captureResolvers.current = {
277
+ resolve: (value) => {
278
+ console.log('[DocScanner] Callback resolver called with:', value);
279
+ captureOriginRef.current = 'auto';
280
+ resolve(value);
281
+ },
282
+ reject: (reason) => {
283
+ console.error('[DocScanner] Callback rejector called with:', reason);
284
+ captureOriginRef.current = 'auto';
285
+ reject(reason);
286
+ },
287
+ };
288
+ });
255
289
  } catch (error) {
256
290
  console.error('[DocScanner] Native capture threw error:', error);
257
291
  captureOriginRef.current = 'auto';
258
292
  return Promise.reject(error);
259
293
  }
260
-
261
- if (result && typeof result.then === 'function') {
262
- console.log('[DocScanner] Native returned a promise, waiting for resolution...');
263
- return result
264
- .then((payload: PictureEvent) => {
265
- console.log('[DocScanner] Native promise resolved with payload:', payload);
266
- handlePictureTaken(payload);
267
- return payload;
268
- })
269
- .catch((error: unknown) => {
270
- console.error('[DocScanner] Native promise rejected:', error);
271
- captureOriginRef.current = 'auto';
272
- throw error;
273
- });
274
- }
275
-
276
- console.log('[DocScanner] Native did not return a promise, using callback-based approach');
277
- return new Promise<PictureEvent>((resolve, reject) => {
278
- captureResolvers.current = {
279
- resolve: (value) => {
280
- console.log('[DocScanner] Callback resolver called with:', value);
281
- captureOriginRef.current = 'auto';
282
- resolve(value);
283
- },
284
- reject: (reason) => {
285
- console.error('[DocScanner] Callback rejector called with:', reason);
286
- captureOriginRef.current = 'auto';
287
- reject(reason);
288
- },
289
- };
290
- });
291
294
  }, [handlePictureTaken]);
292
295
 
293
296
  const handleManualCapture = useCallback(() => {
@@ -1,5 +1,6 @@
1
1
  #import "IPDFCameraViewController.h"
2
2
  #import <React/RCTViewManager.h>
3
+ #import <React/RCTBridgeModule.h>
3
4
 
4
5
  @interface DocumentScannerView : IPDFCameraViewController <IPDFCameraViewControllerDelegate>
5
6
 
@@ -14,5 +15,6 @@
14
15
  @property (nonatomic, assign) BOOL manualOnly;
15
16
 
16
17
  - (void) capture;
18
+ - (void) captureWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject;
17
19
 
18
20
  @end
@@ -124,6 +124,87 @@
124
124
  self.onRectangleDetect(payload);
125
125
  }
126
126
 
127
+ // Helper method to process captured images and prepare response data
128
+ - (NSDictionary *)processAndPrepareImageData:(UIImage *)croppedImage
129
+ initialImage:(UIImage *)initialImage
130
+ rectangleFeature:(CIRectangleFeature *)rectangleFeature {
131
+ CGFloat imageQuality = MAX(self.quality, 0.95);
132
+ NSData *croppedImageData = UIImageJPEGRepresentation(croppedImage, imageQuality);
133
+
134
+ if (initialImage.imageOrientation != UIImageOrientationUp) {
135
+ UIGraphicsBeginImageContextWithOptions(initialImage.size, false, initialImage.scale);
136
+ [initialImage drawInRect:CGRectMake(0, 0, initialImage.size.width, initialImage.size.height)];
137
+ initialImage = UIGraphicsGetImageFromCurrentImageContext();
138
+ UIGraphicsEndImageContext();
139
+ }
140
+ NSData *initialImageData = UIImageJPEGRepresentation(initialImage, imageQuality);
141
+
142
+ NSDictionary *rectangleCoordinatesDict = [self dictionaryForRectangleFeature:rectangleFeature];
143
+ id rectangleCoordinates = rectangleCoordinatesDict ? rectangleCoordinatesDict : [NSNull null];
144
+
145
+ if (self.useBase64) {
146
+ return @{
147
+ @"croppedImage": [croppedImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength],
148
+ @"initialImage": [initialImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength],
149
+ @"rectangleCoordinates": rectangleCoordinates
150
+ };
151
+ } else {
152
+ NSString *dir = NSTemporaryDirectory();
153
+ if (self.saveInAppDocument) {
154
+ dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
155
+ }
156
+ NSString *croppedFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"cropped_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];
157
+ NSString *initialFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"initial_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];
158
+
159
+ [croppedImageData writeToFile:croppedFilePath atomically:YES];
160
+ [initialImageData writeToFile:initialFilePath atomically:YES];
161
+
162
+ return @{
163
+ @"croppedImage": croppedFilePath,
164
+ @"initialImage": initialFilePath,
165
+ @"rectangleCoordinates": rectangleCoordinates
166
+ };
167
+ }
168
+ }
169
+
170
+ // Promise-based capture method - NEW
171
+ - (void)captureWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject {
172
+ NSLog(@"[DocumentScanner] captureWithResolver called");
173
+
174
+ [self captureImageWithCompletionHander:^(UIImage *croppedImage, UIImage *initialImage, CIRectangleFeature *rectangleFeature) {
175
+ NSLog(@"[DocumentScanner] captureImageWithCompletionHander callback - croppedImage: %@, initialImage: %@", croppedImage ? @"YES" : @"NO", initialImage ? @"YES" : @"NO");
176
+
177
+ if (!croppedImage && initialImage) {
178
+ croppedImage = initialImage;
179
+ } else if (!initialImage && croppedImage) {
180
+ initialImage = croppedImage;
181
+ }
182
+
183
+ if (!croppedImage || !initialImage) {
184
+ NSLog(@"[DocumentScanner] capture failed - missing image data");
185
+ reject(@"CAPTURE_FAILED", @"Failed to capture image", nil);
186
+
187
+ if (!self.captureMultiple) {
188
+ [self stop];
189
+ }
190
+ return;
191
+ }
192
+
193
+ NSLog(@"[DocumentScanner] Processing captured images");
194
+ NSDictionary *result = [self processAndPrepareImageData:croppedImage
195
+ initialImage:initialImage
196
+ rectangleFeature:rectangleFeature];
197
+
198
+ NSLog(@"[DocumentScanner] Resolving promise with result");
199
+ resolve(result);
200
+
201
+ if (!self.captureMultiple) {
202
+ [self stop];
203
+ }
204
+ }];
205
+ }
206
+
207
+ // Event-based capture method - LEGACY (for backwards compatibility)
127
208
  - (void) capture {
128
209
  NSLog(@"[DocumentScanner] capture called");
129
210
  [self captureImageWithCompletionHander:^(UIImage *croppedImage, UIImage *initialImage, CIRectangleFeature *rectangleFeature) {
@@ -151,49 +232,10 @@
151
232
 
152
233
  if (self.onPictureTaken) {
153
234
  NSLog(@"[DocumentScanner] Calling onPictureTaken");
154
- // Use maximum JPEG quality (1.0) or user's quality setting, whichever is higher
155
- // This ensures no quality loss during compression
156
- CGFloat imageQuality = MAX(self.quality, 0.95);
157
- NSData *croppedImageData = UIImageJPEGRepresentation(croppedImage, imageQuality);
158
-
159
- if (initialImage.imageOrientation != UIImageOrientationUp) {
160
- UIGraphicsBeginImageContextWithOptions(initialImage.size, false, initialImage.scale);
161
- [initialImage drawInRect:CGRectMake(0, 0, initialImage.size.width
162
- , initialImage.size.height)];
163
- initialImage = UIGraphicsGetImageFromCurrentImageContext();
164
- UIGraphicsEndImageContext();
165
- }
166
- NSData *initialImageData = UIImageJPEGRepresentation(initialImage, imageQuality);
167
-
168
- /*
169
- RectangleCoordinates expects a rectanle viewed from portrait,
170
- while rectangleFeature returns a rectangle viewed from landscape, which explains the nonsense of the mapping below.
171
- Sorry about that.
172
- */
173
- NSDictionary *rectangleCoordinatesDict = [self dictionaryForRectangleFeature:rectangleFeature];
174
- id rectangleCoordinates = rectangleCoordinatesDict ? rectangleCoordinatesDict : [NSNull null];
175
- if (self.useBase64) {
176
- self.onPictureTaken(@{
177
- @"croppedImage": [croppedImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength],
178
- @"initialImage": [initialImageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength],
179
- @"rectangleCoordinates": rectangleCoordinates });
180
- }
181
- else {
182
- NSString *dir = NSTemporaryDirectory();
183
- if (self.saveInAppDocument) {
184
- dir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
185
- }
186
- NSString *croppedFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"cropped_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];
187
- NSString *initialFilePath = [dir stringByAppendingPathComponent:[NSString stringWithFormat:@"initial_img_%i.jpeg",(int)[NSDate date].timeIntervalSince1970]];
188
-
189
- [croppedImageData writeToFile:croppedFilePath atomically:YES];
190
- [initialImageData writeToFile:initialFilePath atomically:YES];
191
-
192
- self.onPictureTaken(@{
193
- @"croppedImage": croppedFilePath,
194
- @"initialImage": initialFilePath,
195
- @"rectangleCoordinates": rectangleCoordinates });
196
- }
235
+ NSDictionary *result = [self processAndPrepareImageData:croppedImage
236
+ initialImage:initialImage
237
+ rectangleFeature:rectangleFeature];
238
+ self.onPictureTaken(result);
197
239
  }
198
240
 
199
241
  if (!self.captureMultiple) {
@@ -34,7 +34,6 @@ static inline void dispatch_async_main_queue(dispatch_block_t block)
34
34
  @property (nonatomic,strong) AVCaptureDevice *captureDevice;
35
35
  @property (nonatomic,strong) EAGLContext *context;
36
36
 
37
- @property (nonatomic, strong) AVCapturePhotoOutput* photoOutput;
38
37
  @property (nonatomic, strong) AVCaptureStillImageOutput* stillImageOutput; // Kept for backward compatibility
39
38
 
40
39
  @property (nonatomic, assign) BOOL forceStop;
@@ -192,29 +191,14 @@ static inline void dispatch_async_main_queue(dispatch_block_t block)
192
191
  [dataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
193
192
  [session addOutput:dataOutput];
194
193
 
195
- // Use modern AVCapturePhotoOutput for iOS 10+
196
- if (@available(iOS 10.0, *)) {
197
- self.photoOutput = [[AVCapturePhotoOutput alloc] init];
198
- if ([session canAddOutput:self.photoOutput]) {
199
- [session addOutput:self.photoOutput];
200
- NSLog(@"[IPDFCamera] Using AVCapturePhotoOutput (modern API)");
201
- } else {
202
- NSLog(@"[IPDFCamera] WARNING: Cannot add AVCapturePhotoOutput, falling back to AVCaptureStillImageOutput");
203
- self.photoOutput = nil;
204
- // Fallback to legacy API
205
- self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
206
- if ([session canAddOutput:self.stillImageOutput]) {
207
- [session addOutput:self.stillImageOutput];
208
- NSLog(@"[IPDFCamera] Fallback successful: Using AVCaptureStillImageOutput");
209
- } else {
210
- NSLog(@"[IPDFCamera] CRITICAL ERROR: Cannot add any capture output!");
211
- }
212
- }
213
- } else {
214
- // Fallback for older iOS versions (< iOS 10)
215
- self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
194
+ // Use legacy AVCaptureStillImageOutput for reliable manual captures on all supported iOS versions
195
+ self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
196
+ if ([session canAddOutput:self.stillImageOutput]) {
216
197
  [session addOutput:self.stillImageOutput];
217
- NSLog(@"[IPDFCamera] Using AVCaptureStillImageOutput (legacy API)");
198
+ NSLog(@"[IPDFCamera] Using AVCaptureStillImageOutput (manual capture)");
199
+ } else {
200
+ NSLog(@"[IPDFCamera] CRITICAL ERROR: Cannot add AVCaptureStillImageOutput to session");
201
+ self.stillImageOutput = nil;
218
202
  }
219
203
 
220
204
  AVCaptureConnection *connection = [dataOutput.connections firstObject];
@@ -520,122 +504,49 @@ static inline void dispatch_async_main_queue(dispatch_block_t block)
520
504
  // Store completion handler for delegate callback
521
505
  self.captureCompletionHandler = completionHandler;
522
506
 
523
- // Use modern AVCapturePhotoOutput API (iOS 10+)
524
- if (@available(iOS 10.0, *)) {
525
- if (self.photoOutput) {
526
- NSLog(@"[IPDFCameraViewController] Using AVCapturePhotoOutput to capture");
527
- AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
528
- [self.photoOutput capturePhotoWithSettings:settings delegate:self];
529
- return;
530
- }
531
-
532
- NSLog(@"[IPDFCameraViewController] photoOutput is nil, trying fallback to stillImageOutput");
533
- // Fallback to legacy API if photoOutput is not available
507
+ if (!self.stillImageOutput) {
508
+ NSLog(@"[IPDFCameraViewController] ERROR: stillImageOutput is nil");
509
+ NSError *error = [NSError errorWithDomain:@"IPDFCameraViewController"
510
+ code:-201
511
+ userInfo:@{ NSLocalizedDescriptionKey: @"missing_still_image_output" }];
512
+ [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
513
+ return;
534
514
  }
535
515
 
536
- // Fallback: Use legacy AVCaptureStillImageOutput (iOS < 10 or when photoOutput failed)
516
+ AVCaptureConnection *videoConnection = nil;
517
+ for (AVCaptureConnection *connection in self.stillImageOutput.connections)
537
518
  {
538
- if (!self.stillImageOutput) {
539
- NSLog(@"[IPDFCameraViewController] ERROR: stillImageOutput is 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];
544
- return;
545
- }
546
-
547
- AVCaptureConnection *videoConnection = nil;
548
- for (AVCaptureConnection *connection in self.stillImageOutput.connections)
519
+ for (AVCaptureInputPort *port in [connection inputPorts])
549
520
  {
550
- for (AVCaptureInputPort *port in [connection inputPorts])
521
+ if ([[port mediaType] isEqual:AVMediaTypeVideo] )
551
522
  {
552
- if ([[port mediaType] isEqual:AVMediaTypeVideo] )
553
- {
554
- videoConnection = connection;
555
- break;
556
- }
523
+ videoConnection = connection;
524
+ break;
557
525
  }
558
- if (videoConnection) break;
559
- }
560
-
561
- if (!videoConnection) {
562
- NSLog(@"[IPDFCameraViewController] ERROR: No video connection found");
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];
567
- return;
568
526
  }
569
-
570
- NSLog(@"[IPDFCameraViewController] Using AVCaptureStillImageOutput (legacy)");
571
- [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
572
- {
573
- [weakSelf handleCapturedImageData:imageSampleBuffer error:error];
574
- }];
575
- }
576
- }
577
-
578
- // AVCapturePhotoCaptureDelegate method for iOS 11+
579
- - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhoto:(AVCapturePhoto *)photo error:(NSError *)error API_AVAILABLE(ios(11.0)) {
580
- NSLog(@"[IPDFCameraViewController] didFinishProcessingPhoto called, error=%@", error);
581
-
582
- if (error) {
583
- NSLog(@"[IPDFCameraViewController] ERROR in didFinishProcessingPhoto: %@", error);
584
- [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
585
- return;
586
- }
587
-
588
- // iOS 11+ uses fileDataRepresentation
589
- NSData *imageData = [photo fileDataRepresentation];
590
- if (!imageData) {
591
- NSLog(@"[IPDFCameraViewController] ERROR: Failed to get image data from photo");
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];
596
- return;
527
+ if (videoConnection) break;
597
528
  }
598
529
 
599
- NSLog(@"[IPDFCameraViewController] Got image data from AVCapturePhoto, size: %lu bytes", (unsigned long)imageData.length);
600
- [self processImageData:imageData];
601
- }
602
-
603
- // AVCapturePhotoCaptureDelegate method for iOS 10
604
- - (void)captureOutput:(AVCapturePhotoOutput *)output didFinishProcessingPhotoSampleBuffer:(CMSampleBufferRef)photoSampleBuffer previewPhotoSampleBuffer:(CMSampleBufferRef)previewPhotoSampleBuffer resolvedSettings:(AVCaptureResolvedPhotoSettings *)resolvedSettings bracketSettings:(AVCaptureBracketedStillImageSettings *)bracketSettings error:(NSError *)error API_DEPRECATED("Use -captureOutput:didFinishProcessingPhoto:error: instead.", ios(10.0, 11.0)) {
605
- NSLog(@"[IPDFCameraViewController] didFinishProcessingPhotoSampleBuffer called (iOS 10)");
606
-
607
- if (error) {
608
- NSLog(@"[IPDFCameraViewController] ERROR in didFinishProcessingPhotoSampleBuffer: %@", error);
530
+ if (!videoConnection) {
531
+ NSLog(@"[IPDFCameraViewController] ERROR: No video connection found");
532
+ NSError *error = [NSError errorWithDomain:@"IPDFCameraViewController"
533
+ code:-202
534
+ userInfo:@{ NSLocalizedDescriptionKey: @"no_video_connection" }];
609
535
  [self completeCaptureWithCroppedImage:nil initialImage:nil rectangle:nil error:error];
610
536
  return;
611
537
  }
612
538
 
613
- if (!photoSampleBuffer) {
614
- NSLog(@"[IPDFCameraViewController] ERROR: photoSampleBuffer is 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];
619
- return;
620
- }
621
-
622
- // iOS 10: Use AVCapturePhotoOutput's method for converting sample buffer
623
- NSData *imageData = [AVCapturePhotoOutput JPEGPhotoDataRepresentationForJPEGSampleBuffer:photoSampleBuffer previewPhotoSampleBuffer:previewPhotoSampleBuffer];
624
-
625
- if (!imageData) {
626
- NSLog(@"[IPDFCameraViewController] ERROR: Failed to create JPEG data from photo sample buffer");
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];
631
- return;
539
+ if (videoConnection.isVideoOrientationSupported) {
540
+ videoConnection.videoOrientation = AVCaptureVideoOrientationPortrait;
632
541
  }
633
542
 
634
- NSLog(@"[IPDFCameraViewController] Got image data from photo sample buffer (iOS 10), size: %lu bytes", (unsigned long)imageData.length);
635
- [self processImageData:imageData];
543
+ NSLog(@"[IPDFCameraViewController] Capturing image via AVCaptureStillImageOutput");
544
+ [self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
545
+ [weakSelf handleCapturedImageData:imageSampleBuffer error:error];
546
+ }];
636
547
  }
637
548
 
638
- // Helper method for legacy AVCaptureStillImageOutput (iOS < 10)
549
+ // Helper method for legacy AVCaptureStillImageOutput
639
550
  - (void)handleCapturedImageData:(CMSampleBufferRef)sampleBuffer error:(NSError *)error {
640
551
  NSLog(@"[IPDFCameraViewController] handleCapturedImageData called (legacy), error=%@, buffer=%@", error, sampleBuffer ? @"YES" : @"NO");
641
552
 
@@ -34,8 +34,10 @@ RCT_EXPORT_VIEW_PROPERTY(quality, float)
34
34
  RCT_EXPORT_VIEW_PROPERTY(brightness, float)
35
35
  RCT_EXPORT_VIEW_PROPERTY(contrast, float)
36
36
 
37
- // Main capture method - uses the last created scanner view
38
- RCT_EXPORT_METHOD(capture:(nullable NSNumber *)reactTag) {
37
+ // Main capture method - returns a Promise
38
+ RCT_EXPORT_METHOD(capture:(nullable NSNumber *)reactTag
39
+ resolver:(RCTPromiseResolveBlock)resolve
40
+ rejecter:(RCTPromiseRejectBlock)reject) {
39
41
  NSLog(@"[RNPdfScannerManager] capture called with reactTag: %@", reactTag);
40
42
  dispatch_async(dispatch_get_main_queue(), ^{
41
43
  DocumentScannerView *targetView = nil;
@@ -59,11 +61,12 @@ RCT_EXPORT_METHOD(capture:(nullable NSNumber *)reactTag) {
59
61
 
60
62
  if (!targetView) {
61
63
  NSLog(@"[RNPdfScannerManager] ERROR: No scanner view available for capture");
64
+ reject(@"NO_VIEW", @"No scanner view available for capture", nil);
62
65
  return;
63
66
  }
64
67
 
65
68
  NSLog(@"[RNPdfScannerManager] Calling capture on view: %@", targetView);
66
- [targetView capture];
69
+ [targetView captureWithResolver:resolve rejecter:reject];
67
70
  });
68
71
  }
69
72
 
@@ -30,13 +30,15 @@ class PdfScanner extends React.Component {
30
30
  console.log('[PdfScanner/ios.js] capture called, ref:', this.scannerRef.current);
31
31
  const handle = findNodeHandle(this.scannerRef.current);
32
32
  console.log('[PdfScanner/ios.js] node handle (reactTag):', handle);
33
- if (handle) {
34
- // Call native method with reactTag
35
- return NativeModules.RNPdfScannerManager.capture(handle);
33
+
34
+ if (!handle) {
35
+ console.error('[PdfScanner/ios.js] ERROR: No handle found for scanner ref');
36
+ return Promise.reject(new Error('No handle found for scanner view'));
36
37
  }
37
- // Fallback to old method
38
- console.log('[PdfScanner/ios.js] No handle, using fallback');
39
- return NativeModules.RNPdfScannerManager.captureGlobal();
38
+
39
+ // Call native method with reactTag - now returns a Promise
40
+ console.log('[PdfScanner/ios.js] Calling native capture with handle:', handle);
41
+ return NativeModules.RNPdfScannerManager.capture(handle);
40
42
  }
41
43
 
42
44
  render() {