react-native-rectangle-doc-scanner 3.67.0 → 3.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/DocScanner.js +35 -32
- package/dist/FullDocScanner.js +26 -17
- package/package.json +1 -1
- package/src/DocScanner.tsx +37 -34
- package/src/FullDocScanner.tsx +27 -18
- package/vendor/react-native-document-scanner/ios/DocumentScannerView.h +2 -0
- package/vendor/react-native-document-scanner/ios/DocumentScannerView.m +85 -43
- package/vendor/react-native-document-scanner/ios/IPDFCameraViewController.m +33 -122
- package/vendor/react-native-document-scanner/ios/RNPdfScannerManager.m +15 -7
- package/vendor/react-native-document-scanner/ios.js +8 -6
package/dist/DocScanner.js
CHANGED
|
@@ -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/dist/FullDocScanner.js
CHANGED
|
@@ -279,35 +279,44 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
279
279
|
}, []);
|
|
280
280
|
const handleRectangleDetect = (0, react_1.useCallback)((event) => {
|
|
281
281
|
const stableCounter = event.stableCounter ?? 0;
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
282
|
+
const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
|
|
283
|
+
const hasRectangle = Boolean(rectangleCoordinates);
|
|
284
|
+
const isConfidentRectangle = hasRectangle && (event.lastDetectionType === 0 || stableCounter > 0);
|
|
285
|
+
const scheduleDetectionClear = () => {
|
|
286
286
|
if (rectangleTimeoutRef.current) {
|
|
287
287
|
clearTimeout(rectangleTimeoutRef.current);
|
|
288
|
-
rectangleTimeoutRef.current = null;
|
|
289
288
|
}
|
|
290
|
-
|
|
289
|
+
rectangleTimeoutRef.current = setTimeout(() => {
|
|
290
|
+
rectangleTimeoutRef.current = null;
|
|
291
|
+
setRectangleDetected((prev) => {
|
|
292
|
+
if (!prev) {
|
|
293
|
+
return prev;
|
|
294
|
+
}
|
|
295
|
+
console.log('[FullDocScanner] Rectangle timeout - clearing detection');
|
|
296
|
+
return false;
|
|
297
|
+
});
|
|
298
|
+
}, 350);
|
|
299
|
+
};
|
|
300
|
+
if (isConfidentRectangle) {
|
|
301
|
+
scheduleDetectionClear();
|
|
302
|
+
setRectangleDetected((prev) => (prev ? prev : true));
|
|
291
303
|
}
|
|
292
|
-
else {
|
|
293
|
-
// Rectangle detected - clear any existing timeout
|
|
304
|
+
else if (!hasRectangle) {
|
|
294
305
|
if (rectangleTimeoutRef.current) {
|
|
295
306
|
clearTimeout(rectangleTimeoutRef.current);
|
|
296
|
-
}
|
|
297
|
-
setRectangleDetected(true);
|
|
298
|
-
// Set timeout to clear rectangle after brief period of no updates
|
|
299
|
-
rectangleTimeoutRef.current = setTimeout(() => {
|
|
300
307
|
rectangleTimeoutRef.current = null;
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
308
|
+
}
|
|
309
|
+
setRectangleDetected(false);
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
// Rectangle is present but confidence is low – keep current state but schedule a clear
|
|
313
|
+
scheduleDetectionClear();
|
|
304
314
|
}
|
|
305
315
|
console.log('[FullDocScanner] Rectangle detection update', {
|
|
306
316
|
lastDetectionType: event.lastDetectionType,
|
|
307
317
|
stableCounter,
|
|
308
318
|
hasRectangle,
|
|
309
|
-
|
|
310
|
-
rectangleDetected: isGoodRectangle,
|
|
319
|
+
isConfidentRectangle,
|
|
311
320
|
});
|
|
312
321
|
}, []);
|
|
313
322
|
(0, react_1.useEffect)(() => () => {
|
package/package.json
CHANGED
package/src/DocScanner.tsx
CHANGED
|
@@ -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(() => {
|
package/src/FullDocScanner.tsx
CHANGED
|
@@ -367,37 +367,46 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
367
367
|
|
|
368
368
|
const handleRectangleDetect = useCallback((event: RectangleDetectEvent) => {
|
|
369
369
|
const stableCounter = event.stableCounter ?? 0;
|
|
370
|
-
const
|
|
371
|
-
const
|
|
370
|
+
const rectangleCoordinates = event.rectangleOnScreen ?? event.rectangleCoordinates;
|
|
371
|
+
const hasRectangle = Boolean(rectangleCoordinates);
|
|
372
|
+
const isConfidentRectangle = hasRectangle && (event.lastDetectionType === 0 || stableCounter > 0);
|
|
372
373
|
|
|
373
|
-
|
|
374
|
-
if (!hasRectangle || !isGoodRectangle) {
|
|
374
|
+
const scheduleDetectionClear = () => {
|
|
375
375
|
if (rectangleTimeoutRef.current) {
|
|
376
376
|
clearTimeout(rectangleTimeoutRef.current);
|
|
377
|
-
rectangleTimeoutRef.current = null;
|
|
378
377
|
}
|
|
379
|
-
setRectangleDetected(false);
|
|
380
|
-
} else {
|
|
381
|
-
// Rectangle detected - clear any existing timeout
|
|
382
|
-
if (rectangleTimeoutRef.current) {
|
|
383
|
-
clearTimeout(rectangleTimeoutRef.current);
|
|
384
|
-
}
|
|
385
|
-
setRectangleDetected(true);
|
|
386
378
|
|
|
387
|
-
// Set timeout to clear rectangle after brief period of no updates
|
|
388
379
|
rectangleTimeoutRef.current = setTimeout(() => {
|
|
389
380
|
rectangleTimeoutRef.current = null;
|
|
390
|
-
setRectangleDetected(
|
|
391
|
-
|
|
392
|
-
|
|
381
|
+
setRectangleDetected((prev) => {
|
|
382
|
+
if (!prev) {
|
|
383
|
+
return prev;
|
|
384
|
+
}
|
|
385
|
+
console.log('[FullDocScanner] Rectangle timeout - clearing detection');
|
|
386
|
+
return false;
|
|
387
|
+
});
|
|
388
|
+
}, 350);
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (isConfidentRectangle) {
|
|
392
|
+
scheduleDetectionClear();
|
|
393
|
+
setRectangleDetected((prev) => (prev ? prev : true));
|
|
394
|
+
} else if (!hasRectangle) {
|
|
395
|
+
if (rectangleTimeoutRef.current) {
|
|
396
|
+
clearTimeout(rectangleTimeoutRef.current);
|
|
397
|
+
rectangleTimeoutRef.current = null;
|
|
398
|
+
}
|
|
399
|
+
setRectangleDetected(false);
|
|
400
|
+
} else {
|
|
401
|
+
// Rectangle is present but confidence is low – keep current state but schedule a clear
|
|
402
|
+
scheduleDetectionClear();
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
console.log('[FullDocScanner] Rectangle detection update', {
|
|
396
406
|
lastDetectionType: event.lastDetectionType,
|
|
397
407
|
stableCounter,
|
|
398
408
|
hasRectangle,
|
|
399
|
-
|
|
400
|
-
rectangleDetected: isGoodRectangle,
|
|
409
|
+
isConfidentRectangle,
|
|
401
410
|
});
|
|
402
411
|
}, []);
|
|
403
412
|
|
|
@@ -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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
196
|
-
|
|
197
|
-
|
|
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 (
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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
|
-
|
|
516
|
+
AVCaptureConnection *videoConnection = nil;
|
|
517
|
+
for (AVCaptureConnection *connection in self.stillImageOutput.connections)
|
|
537
518
|
{
|
|
538
|
-
|
|
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
|
-
|
|
521
|
+
if ([[port mediaType] isEqual:AVMediaTypeVideo] )
|
|
551
522
|
{
|
|
552
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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 (
|
|
614
|
-
|
|
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]
|
|
635
|
-
[self
|
|
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
|
|
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,21 +34,28 @@ 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 -
|
|
38
|
-
RCT_EXPORT_METHOD(capture:(nullable
|
|
37
|
+
// Main capture method - returns a Promise
|
|
38
|
+
RCT_EXPORT_METHOD(capture:(nullable id)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;
|
|
44
|
+
NSNumber *resolvedTag = ([reactTag isKindOfClass:[NSNumber class]]) ? (NSNumber *)reactTag : nil;
|
|
42
45
|
|
|
43
|
-
if (reactTag) {
|
|
44
|
-
|
|
46
|
+
if (!resolvedTag && reactTag) {
|
|
47
|
+
NSLog(@"[RNPdfScannerManager] Unexpected reactTag type %@ - falling back to last known view", NSStringFromClass([reactTag class]));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (resolvedTag) {
|
|
51
|
+
UIView *view = [self.bridge.uiManager viewForReactTag:resolvedTag];
|
|
45
52
|
if ([view isKindOfClass:[DocumentScannerView class]]) {
|
|
46
53
|
targetView = (DocumentScannerView *)view;
|
|
47
54
|
self->_scannerView = targetView;
|
|
48
55
|
} else if (view) {
|
|
49
|
-
NSLog(@"[RNPdfScannerManager] View for tag %@ is not DocumentScannerView: %@",
|
|
56
|
+
NSLog(@"[RNPdfScannerManager] View for tag %@ is not DocumentScannerView: %@", resolvedTag, NSStringFromClass(view.class));
|
|
50
57
|
} else {
|
|
51
|
-
NSLog(@"[RNPdfScannerManager] No view found for tag %@",
|
|
58
|
+
NSLog(@"[RNPdfScannerManager] No view found for tag %@", resolvedTag);
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -59,11 +66,12 @@ RCT_EXPORT_METHOD(capture:(nullable NSNumber *)reactTag) {
|
|
|
59
66
|
|
|
60
67
|
if (!targetView) {
|
|
61
68
|
NSLog(@"[RNPdfScannerManager] ERROR: No scanner view available for capture");
|
|
69
|
+
reject(@"NO_VIEW", @"No scanner view available for capture", nil);
|
|
62
70
|
return;
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
NSLog(@"[RNPdfScannerManager] Calling capture on view: %@", targetView);
|
|
66
|
-
[targetView
|
|
74
|
+
[targetView captureWithResolver:resolve rejecter:reject];
|
|
67
75
|
});
|
|
68
76
|
}
|
|
69
77
|
|
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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() {
|