react-native-rectangle-doc-scanner 3.106.0 → 3.108.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/README.md +1 -1
- package/SETUP.md +1 -1
- package/dist/FullDocScanner.js +89 -20
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +153 -43
package/README.md
CHANGED
|
@@ -120,7 +120,7 @@ Manual capture exposes an imperative `capture()` method via `ref`. Children rend
|
|
|
120
120
|
## Convenience APIs
|
|
121
121
|
|
|
122
122
|
- `CropEditor` – wraps `react-native-perspective-image-cropper` for manual corner adjustment.
|
|
123
|
-
- `FullDocScanner` – puts the scanner and crop editor into a single modal-like flow. If the host app
|
|
123
|
+
- `FullDocScanner` – puts the scanner and crop editor into a single modal-like flow. If the host app links either `expo-image-manipulator` or `react-native-image-rotate`, the confirmation screen exposes 90° rotation buttons; otherwise rotation controls remain hidden.
|
|
124
124
|
|
|
125
125
|
## License
|
|
126
126
|
|
package/SETUP.md
CHANGED
|
@@ -117,7 +117,7 @@ function MyComponent() {
|
|
|
117
117
|
}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
-
> 참고: 최종 확인 화면의 회전 버튼은 프로젝트에 `expo-image-manipulator` 네이티브 모듈이
|
|
120
|
+
> 참고: 최종 확인 화면의 회전 버튼은 프로젝트에 `expo-image-manipulator` 또는 `react-native-image-rotate` 네이티브 모듈이 연결되어 있을 때만 노출됩니다. 둘 중 하나라도 없으면 버튼이 자동으로 숨겨지고 원본 각도로 결과가 반환됩니다.
|
|
121
121
|
|
|
122
122
|
## API 변경사항
|
|
123
123
|
|
package/dist/FullDocScanner.js
CHANGED
|
@@ -44,14 +44,44 @@ const react_native_image_crop_picker_1 = __importDefault(require("react-native-i
|
|
|
44
44
|
const react_native_fs_1 = __importDefault(require("react-native-fs"));
|
|
45
45
|
const DocScanner_1 = require("./DocScanner");
|
|
46
46
|
let ImageManipulator = null;
|
|
47
|
+
let ImageRotate = null;
|
|
48
|
+
const ImageStoreManager = react_native_1.NativeModules.ImageStoreManager;
|
|
47
49
|
try {
|
|
48
50
|
ImageManipulator = require('expo-image-manipulator');
|
|
49
51
|
}
|
|
50
52
|
catch (error) {
|
|
51
|
-
console.warn('[FullDocScanner] expo-image-manipulator module unavailable.
|
|
53
|
+
console.warn('[FullDocScanner] expo-image-manipulator module unavailable. Checking fallback options.', error);
|
|
52
54
|
}
|
|
53
|
-
|
|
55
|
+
try {
|
|
56
|
+
const rotateModule = require('react-native-image-rotate');
|
|
57
|
+
ImageRotate = (rotateModule?.default ?? rotateModule);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.warn('[FullDocScanner] react-native-image-rotate module unavailable. Image rotation fallback disabled.', error);
|
|
61
|
+
}
|
|
62
|
+
const isExpoImageManipulatorAvailable = !!ImageManipulator?.manipulateAsync;
|
|
63
|
+
const isImageRotateAvailable = !!ImageRotate?.rotateImage;
|
|
64
|
+
const isImageRotationSupported = isExpoImageManipulatorAvailable || isImageRotateAvailable;
|
|
54
65
|
const stripFileUri = (value) => value.replace(/^file:\/\//, '');
|
|
66
|
+
const ensureFileUri = (value) => (value.startsWith('file://') ? value : `file://${value}`);
|
|
67
|
+
const getBase64FromImageStore = async (uri) => {
|
|
68
|
+
const getBase64ForTag = ImageStoreManager?.getBase64ForTag?.bind(ImageStoreManager);
|
|
69
|
+
if (!getBase64ForTag) {
|
|
70
|
+
throw new Error('ImageStoreManager.getBase64ForTag unavailable');
|
|
71
|
+
}
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
getBase64ForTag(uri, (base64) => resolve(base64), (error) => {
|
|
74
|
+
const message = typeof error === 'string'
|
|
75
|
+
? error
|
|
76
|
+
: error && typeof error === 'object' && 'message' in error
|
|
77
|
+
? String(error.message)
|
|
78
|
+
: 'Failed to read from ImageStore';
|
|
79
|
+
reject(new Error(message));
|
|
80
|
+
});
|
|
81
|
+
}).finally(() => {
|
|
82
|
+
ImageStoreManager?.removeImageForTag?.(uri);
|
|
83
|
+
});
|
|
84
|
+
};
|
|
55
85
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
56
86
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
57
87
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -351,9 +381,25 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
351
381
|
const handleFlashToggle = (0, react_1.useCallback)(() => {
|
|
352
382
|
setFlashEnabled(prev => !prev);
|
|
353
383
|
}, []);
|
|
384
|
+
const rotateImageWithFallback = (0, react_1.useCallback)((uri, angle) => {
|
|
385
|
+
return new Promise((resolve, reject) => {
|
|
386
|
+
if (!ImageRotate?.rotateImage) {
|
|
387
|
+
reject(new Error('react-native-image-rotate unavailable'));
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
ImageRotate.rotateImage(uri, angle, (rotatedUri) => resolve(rotatedUri), (error) => {
|
|
391
|
+
const message = typeof error === 'string'
|
|
392
|
+
? error
|
|
393
|
+
: error && typeof error === 'object' && 'message' in error
|
|
394
|
+
? String(error.message)
|
|
395
|
+
: 'Unknown rotation error';
|
|
396
|
+
reject(new Error(message));
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
}, []);
|
|
354
400
|
const handleRotateImage = (0, react_1.useCallback)((degrees) => {
|
|
355
401
|
if (!isImageRotationSupported) {
|
|
356
|
-
console.warn('[FullDocScanner] Image rotation requested but
|
|
402
|
+
console.warn('[FullDocScanner] Image rotation requested but no rotation module is available.');
|
|
357
403
|
return;
|
|
358
404
|
}
|
|
359
405
|
// UI만 회전 (실제 파일 회전은 confirm 시 처리)
|
|
@@ -372,10 +418,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
372
418
|
return;
|
|
373
419
|
}
|
|
374
420
|
// 회전이 없거나 모듈을 사용할 수 없으면 기존 데이터 그대로 전달
|
|
375
|
-
if (rotationDegrees === 0
|
|
376
|
-
if (rotationDegrees !== 0 && !isImageRotationSupported) {
|
|
377
|
-
console.warn('[FullDocScanner] Confirm requested with rotation but expo-image-manipulator is unavailable. Returning original image.');
|
|
378
|
-
}
|
|
421
|
+
if (rotationDegrees === 0) {
|
|
379
422
|
onResult({
|
|
380
423
|
path: croppedImageData.path,
|
|
381
424
|
base64: croppedImageData.base64,
|
|
@@ -386,25 +429,51 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
386
429
|
setProcessing(true);
|
|
387
430
|
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
388
431
|
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
389
|
-
|
|
390
|
-
if (!manipulator?.manipulateAsync) {
|
|
391
|
-
console.warn('[FullDocScanner] expo-image-manipulator is unavailable at runtime. Returning original image.');
|
|
432
|
+
if (rotationNormalized === 0) {
|
|
392
433
|
onResult({
|
|
393
434
|
path: croppedImageData.path,
|
|
394
435
|
base64: croppedImageData.base64,
|
|
395
436
|
});
|
|
396
437
|
return;
|
|
397
438
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
439
|
+
if (isExpoImageManipulatorAvailable && ImageManipulator?.manipulateAsync) {
|
|
440
|
+
// expo-image-manipulator로 이미지 회전
|
|
441
|
+
const result = await ImageManipulator.manipulateAsync(croppedImageData.path, [{ rotate: rotationNormalized }], {
|
|
442
|
+
compress: 0.9,
|
|
443
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
444
|
+
base64: true,
|
|
445
|
+
});
|
|
446
|
+
onResult({
|
|
447
|
+
path: result.uri,
|
|
448
|
+
base64: result.base64,
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
453
|
+
const sourceUri = ensureFileUri(croppedImageData.path);
|
|
454
|
+
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
455
|
+
let finalPath = croppedImageData.path;
|
|
456
|
+
let base64Result = croppedImageData.base64;
|
|
457
|
+
try {
|
|
458
|
+
const base64FromStore = await getBase64FromImageStore(rotatedUri);
|
|
459
|
+
const destinationPath = `${react_native_fs_1.default.CachesDirectoryPath}/full-doc-scanner-rotated-${Date.now()}-${Math.floor(Math.random() * 10000)}.jpg`;
|
|
460
|
+
await react_native_fs_1.default.writeFile(destinationPath, base64FromStore, 'base64');
|
|
461
|
+
finalPath = destinationPath;
|
|
462
|
+
base64Result = base64FromStore;
|
|
463
|
+
}
|
|
464
|
+
catch (readError) {
|
|
465
|
+
console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
|
|
466
|
+
}
|
|
467
|
+
onResult({
|
|
468
|
+
path: finalPath,
|
|
469
|
+
base64: base64Result,
|
|
470
|
+
});
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
console.warn('[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.');
|
|
405
474
|
onResult({
|
|
406
|
-
path:
|
|
407
|
-
base64:
|
|
475
|
+
path: croppedImageData.path,
|
|
476
|
+
base64: croppedImageData.base64,
|
|
408
477
|
});
|
|
409
478
|
}
|
|
410
479
|
catch (error) {
|
|
@@ -415,7 +484,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
415
484
|
finally {
|
|
416
485
|
setProcessing(false);
|
|
417
486
|
}
|
|
418
|
-
}, [croppedImageData, rotationDegrees, onResult]);
|
|
487
|
+
}, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
|
|
419
488
|
const handleRetake = (0, react_1.useCallback)(() => {
|
|
420
489
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|
|
421
490
|
setCroppedImageData(null);
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
Text,
|
|
9
9
|
TouchableOpacity,
|
|
10
10
|
View,
|
|
11
|
+
NativeModules,
|
|
11
12
|
} from 'react-native';
|
|
12
13
|
import { launchImageLibrary } from 'react-native-image-picker';
|
|
13
14
|
import ImageCropPicker from 'react-native-image-crop-picker';
|
|
@@ -22,21 +23,79 @@ import type {
|
|
|
22
23
|
} from './DocScanner';
|
|
23
24
|
|
|
24
25
|
type ImageManipulatorModule = typeof import('expo-image-manipulator');
|
|
26
|
+
type ImageRotateModule = {
|
|
27
|
+
rotateImage: (
|
|
28
|
+
uri: string,
|
|
29
|
+
angle: number,
|
|
30
|
+
onSuccess: (uri: string) => void,
|
|
31
|
+
onError: (error: unknown) => void,
|
|
32
|
+
) => void;
|
|
33
|
+
} | null;
|
|
34
|
+
|
|
35
|
+
type ImageStoreModule = {
|
|
36
|
+
getBase64ForTag?: (
|
|
37
|
+
uri: string,
|
|
38
|
+
success: (base64: string) => void,
|
|
39
|
+
failure: (error: unknown) => void,
|
|
40
|
+
) => void;
|
|
41
|
+
removeImageForTag?: (uri: string) => void;
|
|
42
|
+
} | undefined;
|
|
25
43
|
|
|
26
44
|
let ImageManipulator: ImageManipulatorModule | null = null;
|
|
45
|
+
let ImageRotate: ImageRotateModule = null;
|
|
46
|
+
const ImageStoreManager: ImageStoreModule = NativeModules.ImageStoreManager;
|
|
27
47
|
|
|
28
48
|
try {
|
|
29
49
|
ImageManipulator = require('expo-image-manipulator') as ImageManipulatorModule;
|
|
30
50
|
} catch (error) {
|
|
31
51
|
console.warn(
|
|
32
|
-
'[FullDocScanner] expo-image-manipulator module unavailable.
|
|
52
|
+
'[FullDocScanner] expo-image-manipulator module unavailable. Checking fallback options.',
|
|
33
53
|
error,
|
|
34
54
|
);
|
|
35
55
|
}
|
|
36
56
|
|
|
37
|
-
|
|
57
|
+
try {
|
|
58
|
+
const rotateModule = require('react-native-image-rotate');
|
|
59
|
+
ImageRotate = (rotateModule?.default ?? rotateModule) as ImageRotateModule;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn(
|
|
62
|
+
'[FullDocScanner] react-native-image-rotate module unavailable. Image rotation fallback disabled.',
|
|
63
|
+
error,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isExpoImageManipulatorAvailable = !!ImageManipulator?.manipulateAsync;
|
|
68
|
+
const isImageRotateAvailable = !!ImageRotate?.rotateImage;
|
|
69
|
+
const isImageRotationSupported = isExpoImageManipulatorAvailable || isImageRotateAvailable;
|
|
38
70
|
|
|
39
71
|
const stripFileUri = (value: string) => value.replace(/^file:\/\//, '');
|
|
72
|
+
const ensureFileUri = (value: string) => (value.startsWith('file://') ? value : `file://${value}`);
|
|
73
|
+
|
|
74
|
+
const getBase64FromImageStore = async (uri: string): Promise<string> => {
|
|
75
|
+
const getBase64ForTag = ImageStoreManager?.getBase64ForTag?.bind(ImageStoreManager);
|
|
76
|
+
|
|
77
|
+
if (!getBase64ForTag) {
|
|
78
|
+
throw new Error('ImageStoreManager.getBase64ForTag unavailable');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return new Promise<string>((resolve, reject) => {
|
|
82
|
+
getBase64ForTag(
|
|
83
|
+
uri,
|
|
84
|
+
(base64: string) => resolve(base64),
|
|
85
|
+
(error: unknown) => {
|
|
86
|
+
const message =
|
|
87
|
+
typeof error === 'string'
|
|
88
|
+
? error
|
|
89
|
+
: error && typeof error === 'object' && 'message' in error
|
|
90
|
+
? String((error as any).message)
|
|
91
|
+
: 'Failed to read from ImageStore';
|
|
92
|
+
reject(new Error(message));
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
}).finally(() => {
|
|
96
|
+
ImageStoreManager?.removeImageForTag?.(uri);
|
|
97
|
+
});
|
|
98
|
+
};
|
|
40
99
|
|
|
41
100
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
42
101
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
@@ -470,37 +529,58 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
470
529
|
setFlashEnabled(prev => !prev);
|
|
471
530
|
}, []);
|
|
472
531
|
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
'
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
532
|
+
const rotateImageWithFallback = useCallback((uri: string, angle: number) => {
|
|
533
|
+
return new Promise<string>((resolve, reject) => {
|
|
534
|
+
if (!ImageRotate?.rotateImage) {
|
|
535
|
+
reject(new Error('react-native-image-rotate unavailable'));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
480
538
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
539
|
+
ImageRotate.rotateImage(
|
|
540
|
+
uri,
|
|
541
|
+
angle,
|
|
542
|
+
(rotatedUri) => resolve(rotatedUri),
|
|
543
|
+
(error) => {
|
|
544
|
+
const message =
|
|
545
|
+
typeof error === 'string'
|
|
546
|
+
? error
|
|
547
|
+
: error && typeof error === 'object' && 'message' in error
|
|
548
|
+
? String((error as any).message)
|
|
549
|
+
: 'Unknown rotation error';
|
|
550
|
+
reject(new Error(message));
|
|
551
|
+
},
|
|
552
|
+
);
|
|
488
553
|
});
|
|
489
554
|
}, []);
|
|
490
555
|
|
|
556
|
+
const handleRotateImage = useCallback(
|
|
557
|
+
(degrees: -90 | 90) => {
|
|
558
|
+
if (!isImageRotationSupported) {
|
|
559
|
+
console.warn(
|
|
560
|
+
'[FullDocScanner] Image rotation requested but no rotation module is available.',
|
|
561
|
+
);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// UI만 회전 (실제 파일 회전은 confirm 시 처리)
|
|
566
|
+
setRotationDegrees((prev) => {
|
|
567
|
+
const newRotation = prev + degrees;
|
|
568
|
+
// -360 ~ 360 범위로 정규화
|
|
569
|
+
if (newRotation <= -360) return newRotation + 360;
|
|
570
|
+
if (newRotation >= 360) return newRotation - 360;
|
|
571
|
+
return newRotation;
|
|
572
|
+
});
|
|
573
|
+
},
|
|
574
|
+
[],
|
|
575
|
+
);
|
|
576
|
+
|
|
491
577
|
const handleConfirm = useCallback(async () => {
|
|
492
578
|
if (!croppedImageData) {
|
|
493
579
|
return;
|
|
494
580
|
}
|
|
495
581
|
|
|
496
582
|
// 회전이 없거나 모듈을 사용할 수 없으면 기존 데이터 그대로 전달
|
|
497
|
-
if (rotationDegrees === 0
|
|
498
|
-
if (rotationDegrees !== 0 && !isImageRotationSupported) {
|
|
499
|
-
console.warn(
|
|
500
|
-
'[FullDocScanner] Confirm requested with rotation but expo-image-manipulator is unavailable. Returning original image.',
|
|
501
|
-
);
|
|
502
|
-
}
|
|
503
|
-
|
|
583
|
+
if (rotationDegrees === 0) {
|
|
504
584
|
onResult({
|
|
505
585
|
path: croppedImageData.path,
|
|
506
586
|
base64: croppedImageData.base64,
|
|
@@ -514,12 +594,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
514
594
|
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
515
595
|
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
516
596
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
if (!manipulator?.manipulateAsync) {
|
|
520
|
-
console.warn(
|
|
521
|
-
'[FullDocScanner] expo-image-manipulator is unavailable at runtime. Returning original image.',
|
|
522
|
-
);
|
|
597
|
+
if (rotationNormalized === 0) {
|
|
523
598
|
onResult({
|
|
524
599
|
path: croppedImageData.path,
|
|
525
600
|
base64: croppedImageData.base64,
|
|
@@ -527,21 +602,56 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
527
602
|
return;
|
|
528
603
|
}
|
|
529
604
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
605
|
+
if (isExpoImageManipulatorAvailable && ImageManipulator?.manipulateAsync) {
|
|
606
|
+
// expo-image-manipulator로 이미지 회전
|
|
607
|
+
const result = await ImageManipulator.manipulateAsync(
|
|
608
|
+
croppedImageData.path,
|
|
609
|
+
[{ rotate: rotationNormalized }],
|
|
610
|
+
{
|
|
611
|
+
compress: 0.9,
|
|
612
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
613
|
+
base64: true,
|
|
614
|
+
},
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
onResult({
|
|
618
|
+
path: result.uri,
|
|
619
|
+
base64: result.base64,
|
|
620
|
+
});
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
625
|
+
const sourceUri = ensureFileUri(croppedImageData.path);
|
|
626
|
+
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
627
|
+
|
|
628
|
+
let finalPath = croppedImageData.path;
|
|
629
|
+
let base64Result: string | undefined = croppedImageData.base64;
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
const base64FromStore = await getBase64FromImageStore(rotatedUri);
|
|
633
|
+
const destinationPath = `${RNFS.CachesDirectoryPath}/full-doc-scanner-rotated-${Date.now()}-${Math.floor(Math.random() * 10000)}.jpg`;
|
|
634
|
+
|
|
635
|
+
await RNFS.writeFile(destinationPath, base64FromStore, 'base64');
|
|
636
|
+
finalPath = destinationPath;
|
|
637
|
+
base64Result = base64FromStore;
|
|
638
|
+
} catch (readError) {
|
|
639
|
+
console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
|
|
640
|
+
}
|
|
540
641
|
|
|
541
|
-
|
|
642
|
+
onResult({
|
|
643
|
+
path: finalPath,
|
|
644
|
+
base64: base64Result,
|
|
645
|
+
});
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
console.warn(
|
|
650
|
+
'[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.',
|
|
651
|
+
);
|
|
542
652
|
onResult({
|
|
543
|
-
path:
|
|
544
|
-
base64:
|
|
653
|
+
path: croppedImageData.path,
|
|
654
|
+
base64: croppedImageData.base64,
|
|
545
655
|
});
|
|
546
656
|
} catch (error) {
|
|
547
657
|
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
@@ -552,7 +662,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
552
662
|
} finally {
|
|
553
663
|
setProcessing(false);
|
|
554
664
|
}
|
|
555
|
-
}, [croppedImageData, rotationDegrees, onResult]);
|
|
665
|
+
}, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
|
|
556
666
|
|
|
557
667
|
const handleRetake = useCallback(() => {
|
|
558
668
|
console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
|