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.
@@ -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
- const isExpoImageManipulatorAvailable = !!ImageManipulator?.manipulateAsync;
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
- // expo-image-manipulator로 이미지 회전
422
- const result = await ImageManipulator.manipulateAsync(croppedImageData.path, [{ rotate: rotationNormalized }], {
423
- compress: 0.9,
424
- format: ImageManipulator.SaveFormat.JPEG,
425
- base64: true,
426
- });
427
- onResult({
428
- path: result.uri,
429
- base64: result.base64,
430
- });
431
- return;
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
- base64Result = await react_native_fs_1.default.readFile(stripFileUri(rotatedUri), 'base64');
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 generate base64 for rotated image:', readError);
442
- base64Result = undefined;
480
+ console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
443
481
  }
444
482
  onResult({
445
- path: rotatedUri,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.107.0",
3
+ "version": "3.109.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -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
- const isExpoImageManipulatorAvailable = !!ImageManipulator?.manipulateAsync;
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
- // expo-image-manipulator로 이미지 회전
570
- const result = await ImageManipulator.manipulateAsync(
571
- croppedImageData.path,
572
- [{ rotate: rotationNormalized }],
573
- {
574
- compress: 0.9,
575
- format: ImageManipulator.SaveFormat.JPEG,
576
- base64: true,
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
- onResult({
581
- path: result.uri,
582
- base64: result.base64,
583
- });
584
- return;
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
- let base64Result = croppedImageData.base64;
645
+
646
+ let finalPath = croppedImageData.path;
647
+ let base64Result: string | undefined = croppedImageData.base64;
591
648
 
592
649
  try {
593
- base64Result = await RNFS.readFile(stripFileUri(rotatedUri), 'base64');
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 generate base64 for rotated image:', readError);
596
- base64Result = undefined;
657
+ console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
597
658
  }
598
659
 
599
660
  onResult({
600
- path: rotatedUri,
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}