react-native-rectangle-doc-scanner 3.109.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 +54 -43
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +64 -64
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
|
}
|
|
@@ -65,24 +64,6 @@ const isImageRotateAvailable = !!ImageRotate?.rotateImage;
|
|
|
65
64
|
const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isImageRotateAvailable;
|
|
66
65
|
const stripFileUri = (value) => value.replace(/^file:\/\//, '');
|
|
67
66
|
const ensureFileUri = (value) => (value.startsWith('file://') ? value : `file://${value}`);
|
|
68
|
-
const getBase64FromImageStore = async (uri) => {
|
|
69
|
-
const getBase64ForTag = ImageStoreManager?.getBase64ForTag?.bind(ImageStoreManager);
|
|
70
|
-
if (!getBase64ForTag) {
|
|
71
|
-
throw new Error('ImageStoreManager.getBase64ForTag unavailable');
|
|
72
|
-
}
|
|
73
|
-
return new Promise((resolve, reject) => {
|
|
74
|
-
getBase64ForTag(uri, (base64) => resolve(base64), (error) => {
|
|
75
|
-
const message = typeof error === 'string'
|
|
76
|
-
? error
|
|
77
|
-
: error && typeof error === 'object' && 'message' in error
|
|
78
|
-
? String(error.message)
|
|
79
|
-
: 'Failed to read from ImageStore';
|
|
80
|
-
reject(new Error(message));
|
|
81
|
-
});
|
|
82
|
-
}).finally(() => {
|
|
83
|
-
ImageStoreManager?.removeImageForTag?.(uri);
|
|
84
|
-
});
|
|
85
|
-
};
|
|
86
67
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
87
68
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
88
69
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -418,8 +399,9 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
418
399
|
if (!croppedImageData) {
|
|
419
400
|
return;
|
|
420
401
|
}
|
|
421
|
-
// 회전이
|
|
402
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
422
403
|
if (rotationDegrees === 0) {
|
|
404
|
+
console.log('[FullDocScanner] No rotation, returning original image');
|
|
423
405
|
onResult({
|
|
424
406
|
path: croppedImageData.path,
|
|
425
407
|
base64: croppedImageData.base64,
|
|
@@ -430,74 +412,103 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
430
412
|
setProcessing(true);
|
|
431
413
|
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
432
414
|
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
415
|
+
console.log('[FullDocScanner] Rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
|
|
433
416
|
if (rotationNormalized === 0) {
|
|
417
|
+
console.log('[FullDocScanner] Normalized to 0, returning original image');
|
|
434
418
|
onResult({
|
|
435
419
|
path: croppedImageData.path,
|
|
436
420
|
base64: croppedImageData.base64,
|
|
437
421
|
});
|
|
422
|
+
setProcessing(false);
|
|
438
423
|
return;
|
|
439
424
|
}
|
|
425
|
+
// expo-image-manipulator 시도
|
|
440
426
|
if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
|
|
441
427
|
try {
|
|
442
|
-
|
|
443
|
-
const
|
|
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 }], {
|
|
444
431
|
compress: 0.9,
|
|
445
432
|
format: ImageManipulator.SaveFormat.JPEG,
|
|
446
433
|
base64: true,
|
|
447
434
|
});
|
|
448
|
-
|
|
435
|
+
console.log('[FullDocScanner] Rotation complete via expo-image-manipulator:', {
|
|
449
436
|
path: result.uri,
|
|
437
|
+
hasBase64: !!result.base64,
|
|
438
|
+
});
|
|
439
|
+
onResult({
|
|
440
|
+
path: stripFileUri(result.uri),
|
|
450
441
|
base64: result.base64,
|
|
451
442
|
});
|
|
443
|
+
setProcessing(false);
|
|
452
444
|
return;
|
|
453
445
|
}
|
|
454
446
|
catch (manipulatorError) {
|
|
455
447
|
const code = manipulatorError && typeof manipulatorError === 'object' && 'code' in manipulatorError
|
|
456
448
|
? String(manipulatorError.code)
|
|
457
449
|
: undefined;
|
|
450
|
+
console.error('[FullDocScanner] expo-image-manipulator error:', manipulatorError);
|
|
458
451
|
if (code === 'ERR_UNAVAILABLE') {
|
|
459
452
|
expoManipulatorUnavailable = true;
|
|
460
|
-
console.warn('[FullDocScanner] expo-image-manipulator unavailable at runtime.
|
|
453
|
+
console.warn('[FullDocScanner] expo-image-manipulator unavailable at runtime. Trying react-native-image-rotate.');
|
|
461
454
|
}
|
|
462
455
|
else {
|
|
463
456
|
throw manipulatorError;
|
|
464
457
|
}
|
|
465
458
|
}
|
|
466
459
|
}
|
|
460
|
+
// react-native-image-rotate 시도 (ImageStore 사용 안 함)
|
|
467
461
|
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
462
|
+
console.log('[FullDocScanner] Using react-native-image-rotate for rotation');
|
|
468
463
|
const sourceUri = ensureFileUri(croppedImageData.path);
|
|
469
|
-
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
470
|
-
let finalPath = croppedImageData.path;
|
|
471
|
-
let base64Result = croppedImageData.base64;
|
|
472
464
|
try {
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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;
|
|
478
487
|
}
|
|
479
|
-
catch (
|
|
480
|
-
console.
|
|
488
|
+
catch (rotateError) {
|
|
489
|
+
console.error('[FullDocScanner] react-native-image-rotate error:', rotateError);
|
|
490
|
+
throw rotateError;
|
|
481
491
|
}
|
|
482
|
-
onResult({
|
|
483
|
-
path: finalPath,
|
|
484
|
-
base64: base64Result,
|
|
485
|
-
});
|
|
486
|
-
return;
|
|
487
492
|
}
|
|
493
|
+
// 회전 불가능한 경우 경고 후 원본 반환
|
|
488
494
|
console.warn('[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.');
|
|
495
|
+
react_native_1.Alert.alert('알림', '이미지 회전 기능을 사용할 수 없습니다. 원본 이미지를 사용합니다.');
|
|
489
496
|
onResult({
|
|
490
497
|
path: croppedImageData.path,
|
|
491
498
|
base64: croppedImageData.base64,
|
|
492
499
|
});
|
|
500
|
+
setProcessing(false);
|
|
493
501
|
}
|
|
494
502
|
catch (error) {
|
|
495
503
|
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
496
|
-
const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : '';
|
|
497
|
-
react_native_1.Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다: ' + errorMessage);
|
|
498
|
-
}
|
|
499
|
-
finally {
|
|
500
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
|
+
});
|
|
501
512
|
}
|
|
502
513
|
}, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
|
|
503
514
|
const handleRetake = (0, react_1.useCallback)(() => {
|
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;
|
|
@@ -73,32 +62,6 @@ const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isIm
|
|
|
73
62
|
const stripFileUri = (value: string) => value.replace(/^file:\/\//, '');
|
|
74
63
|
const ensureFileUri = (value: string) => (value.startsWith('file://') ? value : `file://${value}`);
|
|
75
64
|
|
|
76
|
-
const getBase64FromImageStore = async (uri: string): Promise<string> => {
|
|
77
|
-
const getBase64ForTag = ImageStoreManager?.getBase64ForTag?.bind(ImageStoreManager);
|
|
78
|
-
|
|
79
|
-
if (!getBase64ForTag) {
|
|
80
|
-
throw new Error('ImageStoreManager.getBase64ForTag unavailable');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return new Promise<string>((resolve, reject) => {
|
|
84
|
-
getBase64ForTag(
|
|
85
|
-
uri,
|
|
86
|
-
(base64: string) => resolve(base64),
|
|
87
|
-
(error: unknown) => {
|
|
88
|
-
const message =
|
|
89
|
-
typeof error === 'string'
|
|
90
|
-
? error
|
|
91
|
-
: error && typeof error === 'object' && 'message' in error
|
|
92
|
-
? String((error as any).message)
|
|
93
|
-
: 'Failed to read from ImageStore';
|
|
94
|
-
reject(new Error(message));
|
|
95
|
-
},
|
|
96
|
-
);
|
|
97
|
-
}).finally(() => {
|
|
98
|
-
ImageStoreManager?.removeImageForTag?.(uri);
|
|
99
|
-
});
|
|
100
|
-
};
|
|
101
|
-
|
|
102
65
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
103
66
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
104
67
|
|
|
@@ -581,8 +544,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
581
544
|
return;
|
|
582
545
|
}
|
|
583
546
|
|
|
584
|
-
// 회전이
|
|
547
|
+
// 회전이 없으면 기존 데이터 그대로 전달
|
|
585
548
|
if (rotationDegrees === 0) {
|
|
549
|
+
console.log('[FullDocScanner] No rotation, returning original image');
|
|
586
550
|
onResult({
|
|
587
551
|
path: croppedImageData.path,
|
|
588
552
|
base64: croppedImageData.base64,
|
|
@@ -595,20 +559,26 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
595
559
|
|
|
596
560
|
// 회전 각도 정규화 (0, 90, 180, 270)
|
|
597
561
|
const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
|
|
562
|
+
console.log('[FullDocScanner] Rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
|
|
598
563
|
|
|
599
564
|
if (rotationNormalized === 0) {
|
|
565
|
+
console.log('[FullDocScanner] Normalized to 0, returning original image');
|
|
600
566
|
onResult({
|
|
601
567
|
path: croppedImageData.path,
|
|
602
568
|
base64: croppedImageData.base64,
|
|
603
569
|
});
|
|
570
|
+
setProcessing(false);
|
|
604
571
|
return;
|
|
605
572
|
}
|
|
606
573
|
|
|
574
|
+
// expo-image-manipulator 시도
|
|
607
575
|
if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
|
|
608
576
|
try {
|
|
609
|
-
|
|
577
|
+
console.log('[FullDocScanner] Using expo-image-manipulator for rotation');
|
|
578
|
+
const inputUri = ensureFileUri(croppedImageData.path);
|
|
579
|
+
|
|
610
580
|
const result = await ImageManipulator.manipulateAsync(
|
|
611
|
-
|
|
581
|
+
inputUri,
|
|
612
582
|
[{ rotate: rotationNormalized }],
|
|
613
583
|
{
|
|
614
584
|
compress: 0.9,
|
|
@@ -617,10 +587,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
617
587
|
},
|
|
618
588
|
);
|
|
619
589
|
|
|
620
|
-
|
|
590
|
+
console.log('[FullDocScanner] Rotation complete via expo-image-manipulator:', {
|
|
621
591
|
path: result.uri,
|
|
592
|
+
hasBase64: !!result.base64,
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
onResult({
|
|
596
|
+
path: stripFileUri(result.uri),
|
|
622
597
|
base64: result.base64,
|
|
623
598
|
});
|
|
599
|
+
setProcessing(false);
|
|
624
600
|
return;
|
|
625
601
|
} catch (manipulatorError) {
|
|
626
602
|
const code =
|
|
@@ -628,10 +604,12 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
628
604
|
? String((manipulatorError as any).code)
|
|
629
605
|
: undefined;
|
|
630
606
|
|
|
607
|
+
console.error('[FullDocScanner] expo-image-manipulator error:', manipulatorError);
|
|
608
|
+
|
|
631
609
|
if (code === 'ERR_UNAVAILABLE') {
|
|
632
610
|
expoManipulatorUnavailable = true;
|
|
633
611
|
console.warn(
|
|
634
|
-
'[FullDocScanner] expo-image-manipulator unavailable at runtime.
|
|
612
|
+
'[FullDocScanner] expo-image-manipulator unavailable at runtime. Trying react-native-image-rotate.',
|
|
635
613
|
);
|
|
636
614
|
} else {
|
|
637
615
|
throw manipulatorError;
|
|
@@ -639,46 +617,68 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
639
617
|
}
|
|
640
618
|
}
|
|
641
619
|
|
|
620
|
+
// react-native-image-rotate 시도 (ImageStore 사용 안 함)
|
|
642
621
|
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
622
|
+
console.log('[FullDocScanner] Using react-native-image-rotate for rotation');
|
|
643
623
|
const sourceUri = ensureFileUri(croppedImageData.path);
|
|
644
|
-
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
645
|
-
|
|
646
|
-
let finalPath = croppedImageData.path;
|
|
647
|
-
let base64Result: string | undefined = croppedImageData.base64;
|
|
648
624
|
|
|
649
625
|
try {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
await RNFS.writeFile(destinationPath, base64FromStore, 'base64');
|
|
654
|
-
finalPath = destinationPath;
|
|
655
|
-
base64Result = base64FromStore;
|
|
656
|
-
} catch (readError) {
|
|
657
|
-
console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
|
|
658
|
-
}
|
|
626
|
+
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
627
|
+
console.log('[FullDocScanner] Image rotated via react-native-image-rotate:', rotatedUri);
|
|
659
628
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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
|
+
}
|
|
665
657
|
}
|
|
666
658
|
|
|
659
|
+
// 회전 불가능한 경우 경고 후 원본 반환
|
|
667
660
|
console.warn(
|
|
668
661
|
'[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.',
|
|
669
662
|
);
|
|
663
|
+
Alert.alert('알림', '이미지 회전 기능을 사용할 수 없습니다. 원본 이미지를 사용합니다.');
|
|
670
664
|
onResult({
|
|
671
665
|
path: croppedImageData.path,
|
|
672
666
|
base64: croppedImageData.base64,
|
|
673
667
|
});
|
|
668
|
+
setProcessing(false);
|
|
674
669
|
} catch (error) {
|
|
675
670
|
console.error('[FullDocScanner] Image rotation error on confirm:', error);
|
|
671
|
+
setProcessing(false);
|
|
676
672
|
|
|
677
673
|
const errorMessage =
|
|
678
|
-
error && typeof error === 'object' && 'message' in error ? (error as Error).message :
|
|
679
|
-
Alert.alert('회전 실패',
|
|
680
|
-
|
|
681
|
-
|
|
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
|
+
});
|
|
682
682
|
}
|
|
683
683
|
}, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
|
|
684
684
|
|