react-native-rectangle-doc-scanner 3.108.0 → 3.110.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/FullDocScanner.js +81 -55
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +98 -80
package/dist/FullDocScanner.js
CHANGED
|
@@ -45,7 +45,6 @@ const react_native_fs_1 = __importDefault(require("react-native-fs"));
|
|
|
45
45
|
const DocScanner_1 = require("./DocScanner");
|
|
46
46
|
let ImageManipulator = null;
|
|
47
47
|
let ImageRotate = null;
|
|
48
|
-
const ImageStoreManager = react_native_1.NativeModules.ImageStoreManager;
|
|
49
48
|
try {
|
|
50
49
|
ImageManipulator = require('expo-image-manipulator');
|
|
51
50
|
}
|
|
@@ -59,29 +58,12 @@ try {
|
|
|
59
58
|
catch (error) {
|
|
60
59
|
console.warn('[FullDocScanner] react-native-image-rotate module unavailable. Image rotation fallback disabled.', error);
|
|
61
60
|
}
|
|
62
|
-
|
|
61
|
+
let expoManipulatorUnavailable = false;
|
|
62
|
+
const isExpoImageManipulatorAvailable = () => !!ImageManipulator?.manipulateAsync && !expoManipulatorUnavailable;
|
|
63
63
|
const isImageRotateAvailable = !!ImageRotate?.rotateImage;
|
|
64
|
-
const isImageRotationSupported = isExpoImageManipulatorAvailable || isImageRotateAvailable;
|
|
64
|
+
const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isImageRotateAvailable;
|
|
65
65
|
const stripFileUri = (value) => value.replace(/^file:\/\//, '');
|
|
66
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
|
-
};
|
|
85
67
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
86
68
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
87
69
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -398,7 +380,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
398
380
|
});
|
|
399
381
|
}, []);
|
|
400
382
|
const handleRotateImage = (0, react_1.useCallback)((degrees) => {
|
|
401
|
-
if (!isImageRotationSupported) {
|
|
383
|
+
if (!isImageRotationSupported()) {
|
|
402
384
|
console.warn('[FullDocScanner] Image rotation requested but no rotation module is available.');
|
|
403
385
|
return;
|
|
404
386
|
}
|
|
@@ -417,8 +399,9 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
417
399
|
if (!croppedImageData) {
|
|
418
400
|
return;
|
|
419
401
|
}
|
|
420
|
-
// 회전이
|
|
402
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
421
403
|
if (rotationDegrees === 0) {
|
|
404
|
+
console.log('[FullDocScanner] No rotation, returning original image');
|
|
422
405
|
onResult({
|
|
423
406
|
path: croppedImageData.path,
|
|
424
407
|
base64: croppedImageData.base64,
|
|
@@ -429,60 +412,103 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
429
412
|
setProcessing(true);
|
|
430
413
|
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
431
414
|
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
415
|
+
console.log('[FullDocScanner] Rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
|
|
432
416
|
if (rotationNormalized === 0) {
|
|
417
|
+
console.log('[FullDocScanner] Normalized to 0, returning original image');
|
|
433
418
|
onResult({
|
|
434
419
|
path: croppedImageData.path,
|
|
435
420
|
base64: croppedImageData.base64,
|
|
436
421
|
});
|
|
422
|
+
setProcessing(false);
|
|
437
423
|
return;
|
|
438
424
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
425
|
+
// expo-image-manipulator 시도
|
|
426
|
+
if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
|
|
427
|
+
try {
|
|
428
|
+
console.log('[FullDocScanner] Using expo-image-manipulator for rotation');
|
|
429
|
+
const inputUri = ensureFileUri(croppedImageData.path);
|
|
430
|
+
const result = await ImageManipulator.manipulateAsync(inputUri, [{ rotate: rotationNormalized }], {
|
|
431
|
+
compress: 0.9,
|
|
432
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
433
|
+
base64: true,
|
|
434
|
+
});
|
|
435
|
+
console.log('[FullDocScanner] Rotation complete via expo-image-manipulator:', {
|
|
436
|
+
path: result.uri,
|
|
437
|
+
hasBase64: !!result.base64,
|
|
438
|
+
});
|
|
439
|
+
onResult({
|
|
440
|
+
path: stripFileUri(result.uri),
|
|
441
|
+
base64: result.base64,
|
|
442
|
+
});
|
|
443
|
+
setProcessing(false);
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
catch (manipulatorError) {
|
|
447
|
+
const code = manipulatorError && typeof manipulatorError === 'object' && 'code' in manipulatorError
|
|
448
|
+
? String(manipulatorError.code)
|
|
449
|
+
: undefined;
|
|
450
|
+
console.error('[FullDocScanner] expo-image-manipulator error:', manipulatorError);
|
|
451
|
+
if (code === 'ERR_UNAVAILABLE') {
|
|
452
|
+
expoManipulatorUnavailable = true;
|
|
453
|
+
console.warn('[FullDocScanner] expo-image-manipulator unavailable at runtime. Trying react-native-image-rotate.');
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
throw manipulatorError;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
451
459
|
}
|
|
460
|
+
// react-native-image-rotate 시도 (ImageStore 사용 안 함)
|
|
452
461
|
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
462
|
+
console.log('[FullDocScanner] Using react-native-image-rotate for rotation');
|
|
453
463
|
const sourceUri = ensureFileUri(croppedImageData.path);
|
|
454
|
-
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
455
|
-
let finalPath = croppedImageData.path;
|
|
456
|
-
let base64Result = croppedImageData.base64;
|
|
457
464
|
try {
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
465
|
+
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
466
|
+
console.log('[FullDocScanner] Image rotated via react-native-image-rotate:', rotatedUri);
|
|
467
|
+
// rotatedUri를 파일로 저장하고 base64 생성
|
|
468
|
+
const destinationPath = `${react_native_fs_1.default.CachesDirectoryPath}/rotated_img_${Date.now()}.jpeg`;
|
|
469
|
+
// rotatedUri는 이미 file://로 시작하므로 stripFileUri로 정리
|
|
470
|
+
const cleanRotatedUri = stripFileUri(rotatedUri);
|
|
471
|
+
// 파일 복사
|
|
472
|
+
await react_native_fs_1.default.copyFile(cleanRotatedUri, destinationPath);
|
|
473
|
+
console.log('[FullDocScanner] Copied rotated file to:', destinationPath);
|
|
474
|
+
// Base64 생성
|
|
475
|
+
const base64Data = await react_native_fs_1.default.readFile(destinationPath, 'base64');
|
|
476
|
+
console.log('[FullDocScanner] Got result:', {
|
|
477
|
+
path: destinationPath,
|
|
478
|
+
hasBase64: true,
|
|
479
|
+
base64Length: base64Data.length,
|
|
480
|
+
});
|
|
481
|
+
onResult({
|
|
482
|
+
path: destinationPath,
|
|
483
|
+
base64: base64Data,
|
|
484
|
+
});
|
|
485
|
+
setProcessing(false);
|
|
486
|
+
return;
|
|
463
487
|
}
|
|
464
|
-
catch (
|
|
465
|
-
console.
|
|
488
|
+
catch (rotateError) {
|
|
489
|
+
console.error('[FullDocScanner] react-native-image-rotate error:', rotateError);
|
|
490
|
+
throw rotateError;
|
|
466
491
|
}
|
|
467
|
-
onResult({
|
|
468
|
-
path: finalPath,
|
|
469
|
-
base64: base64Result,
|
|
470
|
-
});
|
|
471
|
-
return;
|
|
472
492
|
}
|
|
493
|
+
// 회전 불가능한 경우 경고 후 원본 반환
|
|
473
494
|
console.warn('[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.');
|
|
495
|
+
react_native_1.Alert.alert('알림', '이미지 회전 기능을 사용할 수 없습니다. 원본 이미지를 사용합니다.');
|
|
474
496
|
onResult({
|
|
475
497
|
path: croppedImageData.path,
|
|
476
498
|
base64: croppedImageData.base64,
|
|
477
499
|
});
|
|
500
|
+
setProcessing(false);
|
|
478
501
|
}
|
|
479
502
|
catch (error) {
|
|
480
503
|
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
481
|
-
const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : '';
|
|
482
|
-
react_native_1.Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다: ' + errorMessage);
|
|
483
|
-
}
|
|
484
|
-
finally {
|
|
485
504
|
setProcessing(false);
|
|
505
|
+
const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : String(error);
|
|
506
|
+
react_native_1.Alert.alert('회전 실패', `이미지 회전 중 오류가 발생했습니다.\n원본 이미지를 사용합니다.\n\n오류: ${errorMessage}`);
|
|
507
|
+
// 에러 발생 시 원본 이미지 반환
|
|
508
|
+
onResult({
|
|
509
|
+
path: croppedImageData.path,
|
|
510
|
+
base64: croppedImageData.base64,
|
|
511
|
+
});
|
|
486
512
|
}
|
|
487
513
|
}, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
|
|
488
514
|
const handleRetake = (0, react_1.useCallback)(() => {
|
|
@@ -559,7 +585,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
559
585
|
croppedImageData ? (
|
|
560
586
|
// check_DP: Show confirmation screen
|
|
561
587
|
react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
|
|
562
|
-
isImageRotationSupported ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
|
|
588
|
+
isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
|
|
563
589
|
react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.rotateButtonTop, onPress: () => handleRotateImage(-90), accessibilityLabel: "\uC67C\uCABD\uC73C\uB85C 90\uB3C4 \uD68C\uC804", accessibilityRole: "button" },
|
|
564
590
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
|
|
565
591
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateButtonLabel }, "\uC88C\uB85C 90\u00B0")),
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -8,7 +8,6 @@ import {
|
|
|
8
8
|
Text,
|
|
9
9
|
TouchableOpacity,
|
|
10
10
|
View,
|
|
11
|
-
NativeModules,
|
|
12
11
|
} from 'react-native';
|
|
13
12
|
import { launchImageLibrary } from 'react-native-image-picker';
|
|
14
13
|
import ImageCropPicker from 'react-native-image-crop-picker';
|
|
@@ -32,18 +31,8 @@ type ImageRotateModule = {
|
|
|
32
31
|
) => void;
|
|
33
32
|
} | null;
|
|
34
33
|
|
|
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;
|
|
43
|
-
|
|
44
34
|
let ImageManipulator: ImageManipulatorModule | null = null;
|
|
45
35
|
let ImageRotate: ImageRotateModule = null;
|
|
46
|
-
const ImageStoreManager: ImageStoreModule = NativeModules.ImageStoreManager;
|
|
47
36
|
|
|
48
37
|
try {
|
|
49
38
|
ImageManipulator = require('expo-image-manipulator') as ImageManipulatorModule;
|
|
@@ -64,39 +53,15 @@ try {
|
|
|
64
53
|
);
|
|
65
54
|
}
|
|
66
55
|
|
|
67
|
-
|
|
56
|
+
let expoManipulatorUnavailable = false;
|
|
57
|
+
const isExpoImageManipulatorAvailable = () =>
|
|
58
|
+
!!ImageManipulator?.manipulateAsync && !expoManipulatorUnavailable;
|
|
68
59
|
const isImageRotateAvailable = !!ImageRotate?.rotateImage;
|
|
69
|
-
const isImageRotationSupported = isExpoImageManipulatorAvailable || isImageRotateAvailable;
|
|
60
|
+
const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isImageRotateAvailable;
|
|
70
61
|
|
|
71
62
|
const stripFileUri = (value: string) => value.replace(/^file:\/\//, '');
|
|
72
63
|
const ensureFileUri = (value: string) => (value.startsWith('file://') ? value : `file://${value}`);
|
|
73
64
|
|
|
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
|
-
};
|
|
99
|
-
|
|
100
65
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
101
66
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
102
67
|
|
|
@@ -555,7 +520,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
555
520
|
|
|
556
521
|
const handleRotateImage = useCallback(
|
|
557
522
|
(degrees: -90 | 90) => {
|
|
558
|
-
if (!isImageRotationSupported) {
|
|
523
|
+
if (!isImageRotationSupported()) {
|
|
559
524
|
console.warn(
|
|
560
525
|
'[FullDocScanner] Image rotation requested but no rotation module is available.',
|
|
561
526
|
);
|
|
@@ -579,8 +544,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
579
544
|
return;
|
|
580
545
|
}
|
|
581
546
|
|
|
582
|
-
// 회전이
|
|
547
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
583
548
|
if (rotationDegrees === 0) {
|
|
549
|
+
console.log('[FullDocScanner] No rotation, returning original image');
|
|
584
550
|
onResult({
|
|
585
551
|
path: croppedImageData.path,
|
|
586
552
|
base64: croppedImageData.base64,
|
|
@@ -593,74 +559,126 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
593
559
|
|
|
594
560
|
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
595
561
|
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
562
|
+
console.log('[FullDocScanner] Rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
|
|
596
563
|
|
|
597
564
|
if (rotationNormalized === 0) {
|
|
565
|
+
console.log('[FullDocScanner] Normalized to 0, returning original image');
|
|
598
566
|
onResult({
|
|
599
567
|
path: croppedImageData.path,
|
|
600
568
|
base64: croppedImageData.base64,
|
|
601
569
|
});
|
|
570
|
+
setProcessing(false);
|
|
602
571
|
return;
|
|
603
572
|
}
|
|
604
573
|
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
574
|
+
// expo-image-manipulator 시도
|
|
575
|
+
if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
|
|
576
|
+
try {
|
|
577
|
+
console.log('[FullDocScanner] Using expo-image-manipulator for rotation');
|
|
578
|
+
const inputUri = ensureFileUri(croppedImageData.path);
|
|
579
|
+
|
|
580
|
+
const result = await ImageManipulator.manipulateAsync(
|
|
581
|
+
inputUri,
|
|
582
|
+
[{ rotate: rotationNormalized }],
|
|
583
|
+
{
|
|
584
|
+
compress: 0.9,
|
|
585
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
586
|
+
base64: true,
|
|
587
|
+
},
|
|
588
|
+
);
|
|
616
589
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
590
|
+
console.log('[FullDocScanner] Rotation complete via expo-image-manipulator:', {
|
|
591
|
+
path: result.uri,
|
|
592
|
+
hasBase64: !!result.base64,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
onResult({
|
|
596
|
+
path: stripFileUri(result.uri),
|
|
597
|
+
base64: result.base64,
|
|
598
|
+
});
|
|
599
|
+
setProcessing(false);
|
|
600
|
+
return;
|
|
601
|
+
} catch (manipulatorError) {
|
|
602
|
+
const code =
|
|
603
|
+
manipulatorError && typeof manipulatorError === 'object' && 'code' in manipulatorError
|
|
604
|
+
? String((manipulatorError as any).code)
|
|
605
|
+
: undefined;
|
|
606
|
+
|
|
607
|
+
console.error('[FullDocScanner] expo-image-manipulator error:', manipulatorError);
|
|
608
|
+
|
|
609
|
+
if (code === 'ERR_UNAVAILABLE') {
|
|
610
|
+
expoManipulatorUnavailable = true;
|
|
611
|
+
console.warn(
|
|
612
|
+
'[FullDocScanner] expo-image-manipulator unavailable at runtime. Trying react-native-image-rotate.',
|
|
613
|
+
);
|
|
614
|
+
} else {
|
|
615
|
+
throw manipulatorError;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
622
618
|
}
|
|
623
619
|
|
|
620
|
+
// react-native-image-rotate 시도 (ImageStore 사용 안 함)
|
|
624
621
|
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
622
|
+
console.log('[FullDocScanner] Using react-native-image-rotate for rotation');
|
|
625
623
|
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
624
|
|
|
631
625
|
try {
|
|
632
|
-
const
|
|
633
|
-
|
|
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
|
-
}
|
|
626
|
+
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
627
|
+
console.log('[FullDocScanner] Image rotated via react-native-image-rotate:', rotatedUri);
|
|
641
628
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
629
|
+
// rotatedUri를 파일로 저장하고 base64 생성
|
|
630
|
+
const destinationPath = `${RNFS.CachesDirectoryPath}/rotated_img_${Date.now()}.jpeg`;
|
|
631
|
+
|
|
632
|
+
// rotatedUri는 이미 file://로 시작하므로 stripFileUri로 정리
|
|
633
|
+
const cleanRotatedUri = stripFileUri(rotatedUri);
|
|
634
|
+
|
|
635
|
+
// 파일 복사
|
|
636
|
+
await RNFS.copyFile(cleanRotatedUri, destinationPath);
|
|
637
|
+
console.log('[FullDocScanner] Copied rotated file to:', destinationPath);
|
|
638
|
+
|
|
639
|
+
// Base64 생성
|
|
640
|
+
const base64Data = await RNFS.readFile(destinationPath, 'base64');
|
|
641
|
+
console.log('[FullDocScanner] Got result:', {
|
|
642
|
+
path: destinationPath,
|
|
643
|
+
hasBase64: true,
|
|
644
|
+
base64Length: base64Data.length,
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
onResult({
|
|
648
|
+
path: destinationPath,
|
|
649
|
+
base64: base64Data,
|
|
650
|
+
});
|
|
651
|
+
setProcessing(false);
|
|
652
|
+
return;
|
|
653
|
+
} catch (rotateError) {
|
|
654
|
+
console.error('[FullDocScanner] react-native-image-rotate error:', rotateError);
|
|
655
|
+
throw rotateError;
|
|
656
|
+
}
|
|
647
657
|
}
|
|
648
658
|
|
|
659
|
+
// 회전 불가능한 경우 경고 후 원본 반환
|
|
649
660
|
console.warn(
|
|
650
661
|
'[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.',
|
|
651
662
|
);
|
|
663
|
+
Alert.alert('알림', '이미지 회전 기능을 사용할 수 없습니다. 원본 이미지를 사용합니다.');
|
|
652
664
|
onResult({
|
|
653
665
|
path: croppedImageData.path,
|
|
654
666
|
base64: croppedImageData.base64,
|
|
655
667
|
});
|
|
668
|
+
setProcessing(false);
|
|
656
669
|
} catch (error) {
|
|
657
670
|
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
671
|
+
setProcessing(false);
|
|
658
672
|
|
|
659
673
|
const errorMessage =
|
|
660
|
-
error && typeof error === 'object' && 'message' in error ? (error as Error).message :
|
|
661
|
-
Alert.alert('회전 실패',
|
|
662
|
-
|
|
663
|
-
|
|
674
|
+
error && typeof error === 'object' && 'message' in error ? (error as Error).message : String(error);
|
|
675
|
+
Alert.alert('회전 실패', `이미지 회전 중 오류가 발생했습니다.\n원본 이미지를 사용합니다.\n\n오류: ${errorMessage}`);
|
|
676
|
+
|
|
677
|
+
// 에러 발생 시 원본 이미지 반환
|
|
678
|
+
onResult({
|
|
679
|
+
path: croppedImageData.path,
|
|
680
|
+
base64: croppedImageData.base64,
|
|
681
|
+
});
|
|
664
682
|
}
|
|
665
683
|
}, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
|
|
666
684
|
|
|
@@ -750,7 +768,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
750
768
|
// check_DP: Show confirmation screen
|
|
751
769
|
<View style={styles.confirmationContainer}>
|
|
752
770
|
{/* 회전 버튼들 - 가운데 정렬 */}
|
|
753
|
-
{isImageRotationSupported ? (
|
|
771
|
+
{isImageRotationSupported() ? (
|
|
754
772
|
<View style={styles.rotateButtonsCenter}>
|
|
755
773
|
<TouchableOpacity
|
|
756
774
|
style={styles.rotateButtonTop}
|