react-native-rectangle-doc-scanner 3.107.0 → 3.109.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 +58 -20
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +86 -25
package/dist/FullDocScanner.js
CHANGED
|
@@ -45,6 +45,7 @@ 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;
|
|
48
49
|
try {
|
|
49
50
|
ImageManipulator = require('expo-image-manipulator');
|
|
50
51
|
}
|
|
@@ -58,11 +59,30 @@ try {
|
|
|
58
59
|
catch (error) {
|
|
59
60
|
console.warn('[FullDocScanner] react-native-image-rotate module unavailable. Image rotation fallback disabled.', error);
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
let expoManipulatorUnavailable = false;
|
|
63
|
+
const isExpoImageManipulatorAvailable = () => !!ImageManipulator?.manipulateAsync && !expoManipulatorUnavailable;
|
|
62
64
|
const isImageRotateAvailable = !!ImageRotate?.rotateImage;
|
|
63
|
-
const isImageRotationSupported = isExpoImageManipulatorAvailable || isImageRotateAvailable;
|
|
65
|
+
const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isImageRotateAvailable;
|
|
64
66
|
const stripFileUri = (value) => value.replace(/^file:\/\//, '');
|
|
65
67
|
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
|
+
};
|
|
66
86
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
67
87
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
68
88
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -379,7 +399,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
379
399
|
});
|
|
380
400
|
}, []);
|
|
381
401
|
const handleRotateImage = (0, react_1.useCallback)((degrees) => {
|
|
382
|
-
if (!isImageRotationSupported) {
|
|
402
|
+
if (!isImageRotationSupported()) {
|
|
383
403
|
console.warn('[FullDocScanner] Image rotation requested but no rotation module is available.');
|
|
384
404
|
return;
|
|
385
405
|
}
|
|
@@ -417,32 +437,50 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
417
437
|
});
|
|
418
438
|
return;
|
|
419
439
|
}
|
|
420
|
-
if (isExpoImageManipulatorAvailable && ImageManipulator?.manipulateAsync) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
440
|
+
if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
|
|
441
|
+
try {
|
|
442
|
+
// expo-image-manipulator로 이미지 회전
|
|
443
|
+
const result = await ImageManipulator.manipulateAsync(croppedImageData.path, [{ rotate: rotationNormalized }], {
|
|
444
|
+
compress: 0.9,
|
|
445
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
446
|
+
base64: true,
|
|
447
|
+
});
|
|
448
|
+
onResult({
|
|
449
|
+
path: result.uri,
|
|
450
|
+
base64: result.base64,
|
|
451
|
+
});
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
catch (manipulatorError) {
|
|
455
|
+
const code = manipulatorError && typeof manipulatorError === 'object' && 'code' in manipulatorError
|
|
456
|
+
? String(manipulatorError.code)
|
|
457
|
+
: undefined;
|
|
458
|
+
if (code === 'ERR_UNAVAILABLE') {
|
|
459
|
+
expoManipulatorUnavailable = true;
|
|
460
|
+
console.warn('[FullDocScanner] expo-image-manipulator unavailable at runtime. Falling back to react-native-image-rotate if possible.');
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
throw manipulatorError;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
432
466
|
}
|
|
433
467
|
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
434
468
|
const sourceUri = ensureFileUri(croppedImageData.path);
|
|
435
469
|
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
470
|
+
let finalPath = croppedImageData.path;
|
|
436
471
|
let base64Result = croppedImageData.base64;
|
|
437
472
|
try {
|
|
438
|
-
|
|
473
|
+
const base64FromStore = await getBase64FromImageStore(rotatedUri);
|
|
474
|
+
const destinationPath = `${react_native_fs_1.default.CachesDirectoryPath}/full-doc-scanner-rotated-${Date.now()}-${Math.floor(Math.random() * 10000)}.jpg`;
|
|
475
|
+
await react_native_fs_1.default.writeFile(destinationPath, base64FromStore, 'base64');
|
|
476
|
+
finalPath = destinationPath;
|
|
477
|
+
base64Result = base64FromStore;
|
|
439
478
|
}
|
|
440
479
|
catch (readError) {
|
|
441
|
-
console.warn('[FullDocScanner] Failed to
|
|
442
|
-
base64Result = undefined;
|
|
480
|
+
console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
|
|
443
481
|
}
|
|
444
482
|
onResult({
|
|
445
|
-
path:
|
|
483
|
+
path: finalPath,
|
|
446
484
|
base64: base64Result,
|
|
447
485
|
});
|
|
448
486
|
return;
|
|
@@ -536,7 +574,7 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
536
574
|
croppedImageData ? (
|
|
537
575
|
// check_DP: Show confirmation screen
|
|
538
576
|
react_1.default.createElement(react_native_1.View, { style: styles.confirmationContainer },
|
|
539
|
-
isImageRotationSupported ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
|
|
577
|
+
isImageRotationSupported() ? (react_1.default.createElement(react_native_1.View, { style: styles.rotateButtonsCenter },
|
|
540
578
|
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" },
|
|
541
579
|
react_1.default.createElement(react_native_1.Text, { style: styles.rotateIconText }, "\u21BA"),
|
|
542
580
|
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,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';
|
|
@@ -31,8 +32,18 @@ type ImageRotateModule = {
|
|
|
31
32
|
) => void;
|
|
32
33
|
} | null;
|
|
33
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;
|
|
43
|
+
|
|
34
44
|
let ImageManipulator: ImageManipulatorModule | null = null;
|
|
35
45
|
let ImageRotate: ImageRotateModule = null;
|
|
46
|
+
const ImageStoreManager: ImageStoreModule = NativeModules.ImageStoreManager;
|
|
36
47
|
|
|
37
48
|
try {
|
|
38
49
|
ImageManipulator = require('expo-image-manipulator') as ImageManipulatorModule;
|
|
@@ -53,13 +64,41 @@ try {
|
|
|
53
64
|
);
|
|
54
65
|
}
|
|
55
66
|
|
|
56
|
-
|
|
67
|
+
let expoManipulatorUnavailable = false;
|
|
68
|
+
const isExpoImageManipulatorAvailable = () =>
|
|
69
|
+
!!ImageManipulator?.manipulateAsync && !expoManipulatorUnavailable;
|
|
57
70
|
const isImageRotateAvailable = !!ImageRotate?.rotateImage;
|
|
58
|
-
const isImageRotationSupported = isExpoImageManipulatorAvailable || isImageRotateAvailable;
|
|
71
|
+
const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isImageRotateAvailable;
|
|
59
72
|
|
|
60
73
|
const stripFileUri = (value: string) => value.replace(/^file:\/\//, '');
|
|
61
74
|
const ensureFileUri = (value: string) => (value.startsWith('file://') ? value : `file://${value}`);
|
|
62
75
|
|
|
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
|
+
|
|
63
102
|
const CROPPER_TIMEOUT_MS = 8000;
|
|
64
103
|
const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
|
|
65
104
|
|
|
@@ -518,7 +557,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
518
557
|
|
|
519
558
|
const handleRotateImage = useCallback(
|
|
520
559
|
(degrees: -90 | 90) => {
|
|
521
|
-
if (!isImageRotationSupported) {
|
|
560
|
+
if (!isImageRotationSupported()) {
|
|
522
561
|
console.warn(
|
|
523
562
|
'[FullDocScanner] Image rotation requested but no rotation module is available.',
|
|
524
563
|
);
|
|
@@ -565,39 +604,61 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
565
604
|
return;
|
|
566
605
|
}
|
|
567
606
|
|
|
568
|
-
if (isExpoImageManipulatorAvailable && ImageManipulator?.manipulateAsync) {
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
607
|
+
if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
|
|
608
|
+
try {
|
|
609
|
+
// expo-image-manipulator로 이미지 회전
|
|
610
|
+
const result = await ImageManipulator.manipulateAsync(
|
|
611
|
+
croppedImageData.path,
|
|
612
|
+
[{ rotate: rotationNormalized }],
|
|
613
|
+
{
|
|
614
|
+
compress: 0.9,
|
|
615
|
+
format: ImageManipulator.SaveFormat.JPEG,
|
|
616
|
+
base64: true,
|
|
617
|
+
},
|
|
618
|
+
);
|
|
579
619
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
620
|
+
onResult({
|
|
621
|
+
path: result.uri,
|
|
622
|
+
base64: result.base64,
|
|
623
|
+
});
|
|
624
|
+
return;
|
|
625
|
+
} catch (manipulatorError) {
|
|
626
|
+
const code =
|
|
627
|
+
manipulatorError && typeof manipulatorError === 'object' && 'code' in manipulatorError
|
|
628
|
+
? String((manipulatorError as any).code)
|
|
629
|
+
: undefined;
|
|
630
|
+
|
|
631
|
+
if (code === 'ERR_UNAVAILABLE') {
|
|
632
|
+
expoManipulatorUnavailable = true;
|
|
633
|
+
console.warn(
|
|
634
|
+
'[FullDocScanner] expo-image-manipulator unavailable at runtime. Falling back to react-native-image-rotate if possible.',
|
|
635
|
+
);
|
|
636
|
+
} else {
|
|
637
|
+
throw manipulatorError;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
585
640
|
}
|
|
586
641
|
|
|
587
642
|
if (isImageRotateAvailable && ImageRotate?.rotateImage) {
|
|
588
643
|
const sourceUri = ensureFileUri(croppedImageData.path);
|
|
589
644
|
const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
|
|
590
|
-
|
|
645
|
+
|
|
646
|
+
let finalPath = croppedImageData.path;
|
|
647
|
+
let base64Result: string | undefined = croppedImageData.base64;
|
|
591
648
|
|
|
592
649
|
try {
|
|
593
|
-
|
|
650
|
+
const base64FromStore = await getBase64FromImageStore(rotatedUri);
|
|
651
|
+
const destinationPath = `${RNFS.CachesDirectoryPath}/full-doc-scanner-rotated-${Date.now()}-${Math.floor(Math.random() * 10000)}.jpg`;
|
|
652
|
+
|
|
653
|
+
await RNFS.writeFile(destinationPath, base64FromStore, 'base64');
|
|
654
|
+
finalPath = destinationPath;
|
|
655
|
+
base64Result = base64FromStore;
|
|
594
656
|
} catch (readError) {
|
|
595
|
-
console.warn('[FullDocScanner] Failed to
|
|
596
|
-
base64Result = undefined;
|
|
657
|
+
console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
|
|
597
658
|
}
|
|
598
659
|
|
|
599
660
|
onResult({
|
|
600
|
-
path:
|
|
661
|
+
path: finalPath,
|
|
601
662
|
base64: base64Result,
|
|
602
663
|
});
|
|
603
664
|
return;
|
|
@@ -707,7 +768,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
707
768
|
// check_DP: Show confirmation screen
|
|
708
769
|
<View style={styles.confirmationContainer}>
|
|
709
770
|
{/* 회전 버튼들 - 가운데 정렬 */}
|
|
710
|
-
{isImageRotationSupported ? (
|
|
771
|
+
{isImageRotationSupported() ? (
|
|
711
772
|
<View style={styles.rotateButtonsCenter}>
|
|
712
773
|
<TouchableOpacity
|
|
713
774
|
style={styles.rotateButtonTop}
|