react-native-rectangle-doc-scanner 3.109.0 → 3.111.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.
@@ -44,45 +44,17 @@ 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;
49
47
  try {
50
48
  ImageManipulator = require('expo-image-manipulator');
51
49
  }
52
50
  catch (error) {
53
- console.warn('[FullDocScanner] expo-image-manipulator module unavailable. Checking fallback options.', error);
54
- }
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);
51
+ console.warn('[FullDocScanner] expo-image-manipulator module unavailable.', error);
61
52
  }
62
53
  let expoManipulatorUnavailable = false;
63
54
  const isExpoImageManipulatorAvailable = () => !!ImageManipulator?.manipulateAsync && !expoManipulatorUnavailable;
64
- const isImageRotateAvailable = !!ImageRotate?.rotateImage;
65
- const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isImageRotateAvailable;
55
+ const isImageRotationSupported = () => isExpoImageManipulatorAvailable();
66
56
  const stripFileUri = (value) => value.replace(/^file:\/\//, '');
67
57
  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
58
  const CROPPER_TIMEOUT_MS = 8000;
87
59
  const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
88
60
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -382,22 +354,6 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
382
354
  const handleFlashToggle = (0, react_1.useCallback)(() => {
383
355
  setFlashEnabled(prev => !prev);
384
356
  }, []);
385
- const rotateImageWithFallback = (0, react_1.useCallback)((uri, angle) => {
386
- return new Promise((resolve, reject) => {
387
- if (!ImageRotate?.rotateImage) {
388
- reject(new Error('react-native-image-rotate unavailable'));
389
- return;
390
- }
391
- ImageRotate.rotateImage(uri, angle, (rotatedUri) => resolve(rotatedUri), (error) => {
392
- const message = typeof error === 'string'
393
- ? error
394
- : error && typeof error === 'object' && 'message' in error
395
- ? String(error.message)
396
- : 'Unknown rotation error';
397
- reject(new Error(message));
398
- });
399
- });
400
- }, []);
401
357
  const handleRotateImage = (0, react_1.useCallback)((degrees) => {
402
358
  if (!isImageRotationSupported()) {
403
359
  console.warn('[FullDocScanner] Image rotation requested but no rotation module is available.');
@@ -418,8 +374,9 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
418
374
  if (!croppedImageData) {
419
375
  return;
420
376
  }
421
- // 회전이 없거나 모듈을 사용할 수 없으면 기존 데이터 그대로 전달
377
+ // 회전이 없으면 기존 데이터 그대로 전달
422
378
  if (rotationDegrees === 0) {
379
+ console.log('[FullDocScanner] No rotation, returning original image');
423
380
  onResult({
424
381
  path: croppedImageData.path,
425
382
  base64: croppedImageData.base64,
@@ -430,76 +387,82 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
430
387
  setProcessing(true);
431
388
  // 회전 각도 정규화 (0, 90, 180, 270)
432
389
  const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
390
+ console.log('[FullDocScanner] Rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
433
391
  if (rotationNormalized === 0) {
392
+ console.log('[FullDocScanner] Normalized to 0, returning original image');
434
393
  onResult({
435
394
  path: croppedImageData.path,
436
395
  base64: croppedImageData.base64,
437
396
  });
397
+ setProcessing(false);
438
398
  return;
439
399
  }
400
+ // expo-image-manipulator 시도
440
401
  if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
441
402
  try {
442
- // expo-image-manipulator 이미지 회전
443
- const result = await ImageManipulator.manipulateAsync(croppedImageData.path, [{ rotate: rotationNormalized }], {
403
+ console.log('[FullDocScanner] Using expo-image-manipulator for rotation');
404
+ const inputUri = ensureFileUri(croppedImageData.path);
405
+ const result = await ImageManipulator.manipulateAsync(inputUri, [{ rotate: rotationNormalized }], {
444
406
  compress: 0.9,
445
407
  format: ImageManipulator.SaveFormat.JPEG,
446
408
  base64: true,
447
409
  });
448
- onResult({
410
+ console.log('[FullDocScanner] Rotation complete via expo-image-manipulator:', {
449
411
  path: result.uri,
412
+ hasBase64: !!result.base64,
413
+ });
414
+ onResult({
415
+ path: stripFileUri(result.uri),
450
416
  base64: result.base64,
451
417
  });
418
+ setProcessing(false);
452
419
  return;
453
420
  }
454
421
  catch (manipulatorError) {
455
422
  const code = manipulatorError && typeof manipulatorError === 'object' && 'code' in manipulatorError
456
423
  ? String(manipulatorError.code)
457
424
  : undefined;
425
+ console.error('[FullDocScanner] expo-image-manipulator error:', manipulatorError);
458
426
  if (code === 'ERR_UNAVAILABLE') {
459
427
  expoManipulatorUnavailable = true;
460
- console.warn('[FullDocScanner] expo-image-manipulator unavailable at runtime. Falling back to react-native-image-rotate if possible.');
428
+ console.warn('[FullDocScanner] expo-image-manipulator unavailable at runtime. Trying react-native-image-rotate.');
461
429
  }
462
430
  else {
463
431
  throw manipulatorError;
464
432
  }
465
433
  }
466
434
  }
467
- if (isImageRotateAvailable && ImageRotate?.rotateImage) {
468
- const sourceUri = ensureFileUri(croppedImageData.path);
469
- const rotatedUri = await rotateImageWithFallback(sourceUri, rotationNormalized);
470
- let finalPath = croppedImageData.path;
471
- let base64Result = croppedImageData.base64;
472
- try {
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;
478
- }
479
- catch (readError) {
480
- console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
481
- }
482
- onResult({
483
- path: finalPath,
484
- base64: base64Result,
485
- });
486
- return;
487
- }
488
- console.warn('[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.');
489
- onResult({
490
- path: croppedImageData.path,
491
- base64: croppedImageData.base64,
492
- });
435
+ // expo-image-manipulator를 사용할 없는 경우 에러 처리
436
+ console.error('[FullDocScanner] Rotation requested but expo-image-manipulator is not available.');
437
+ setProcessing(false);
438
+ react_native_1.Alert.alert('회전 불가', 'expo-image-manipulator가 설치되지 않아 이미지 회전을 수행할 수 없습니다.\n\n패키지를 설치해주세요:\nnpm install expo-image-manipulator', [
439
+ {
440
+ text: '원본 사용',
441
+ onPress: () => {
442
+ onResult({
443
+ path: croppedImageData.path,
444
+ base64: croppedImageData.base64,
445
+ });
446
+ },
447
+ },
448
+ {
449
+ text: '취소',
450
+ style: 'cancel',
451
+ },
452
+ ]);
493
453
  }
494
454
  catch (error) {
495
455
  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
456
  setProcessing(false);
457
+ const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : String(error);
458
+ react_native_1.Alert.alert('회전 실패', `이미지 회전 중 오류가 발생했습니다.\n원본 이미지를 사용합니다.\n\n오류: ${errorMessage}`);
459
+ // 에러 발생 시 원본 이미지 반환
460
+ onResult({
461
+ path: croppedImageData.path,
462
+ base64: croppedImageData.base64,
463
+ });
501
464
  }
502
- }, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
465
+ }, [croppedImageData, rotationDegrees, onResult]);
503
466
  const handleRetake = (0, react_1.useCallback)(() => {
504
467
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');
505
468
  setCroppedImageData(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.109.0",
3
+ "version": "3.111.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,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';
@@ -23,43 +22,14 @@ import type {
23
22
  } from './DocScanner';
24
23
 
25
24
  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;
43
25
 
44
26
  let ImageManipulator: ImageManipulatorModule | null = null;
45
- let ImageRotate: ImageRotateModule = null;
46
- const ImageStoreManager: ImageStoreModule = NativeModules.ImageStoreManager;
47
27
 
48
28
  try {
49
29
  ImageManipulator = require('expo-image-manipulator') as ImageManipulatorModule;
50
30
  } catch (error) {
51
31
  console.warn(
52
- '[FullDocScanner] expo-image-manipulator module unavailable. Checking fallback options.',
53
- error,
54
- );
55
- }
56
-
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.',
32
+ '[FullDocScanner] expo-image-manipulator module unavailable.',
63
33
  error,
64
34
  );
65
35
  }
@@ -67,38 +37,11 @@ try {
67
37
  let expoManipulatorUnavailable = false;
68
38
  const isExpoImageManipulatorAvailable = () =>
69
39
  !!ImageManipulator?.manipulateAsync && !expoManipulatorUnavailable;
70
- const isImageRotateAvailable = !!ImageRotate?.rotateImage;
71
- const isImageRotationSupported = () => isExpoImageManipulatorAvailable() || isImageRotateAvailable;
40
+ const isImageRotationSupported = () => isExpoImageManipulatorAvailable();
72
41
 
73
42
  const stripFileUri = (value: string) => value.replace(/^file:\/\//, '');
74
43
  const ensureFileUri = (value: string) => (value.startsWith('file://') ? value : `file://${value}`);
75
44
 
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
45
  const CROPPER_TIMEOUT_MS = 8000;
103
46
  const CROPPER_TIMEOUT_CODE = 'cropper_timeout';
104
47
 
@@ -531,30 +474,6 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
531
474
  setFlashEnabled(prev => !prev);
532
475
  }, []);
533
476
 
534
- const rotateImageWithFallback = useCallback((uri: string, angle: number) => {
535
- return new Promise<string>((resolve, reject) => {
536
- if (!ImageRotate?.rotateImage) {
537
- reject(new Error('react-native-image-rotate unavailable'));
538
- return;
539
- }
540
-
541
- ImageRotate.rotateImage(
542
- uri,
543
- angle,
544
- (rotatedUri) => resolve(rotatedUri),
545
- (error) => {
546
- const message =
547
- typeof error === 'string'
548
- ? error
549
- : error && typeof error === 'object' && 'message' in error
550
- ? String((error as any).message)
551
- : 'Unknown rotation error';
552
- reject(new Error(message));
553
- },
554
- );
555
- });
556
- }, []);
557
-
558
477
  const handleRotateImage = useCallback(
559
478
  (degrees: -90 | 90) => {
560
479
  if (!isImageRotationSupported()) {
@@ -581,8 +500,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
581
500
  return;
582
501
  }
583
502
 
584
- // 회전이 없거나 모듈을 사용할 수 없으면 기존 데이터 그대로 전달
503
+ // 회전이 없으면 기존 데이터 그대로 전달
585
504
  if (rotationDegrees === 0) {
505
+ console.log('[FullDocScanner] No rotation, returning original image');
586
506
  onResult({
587
507
  path: croppedImageData.path,
588
508
  base64: croppedImageData.base64,
@@ -595,20 +515,26 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
595
515
 
596
516
  // 회전 각도 정규화 (0, 90, 180, 270)
597
517
  const rotationNormalized = ((rotationDegrees % 360) + 360) % 360;
518
+ console.log('[FullDocScanner] Rotation degrees:', rotationDegrees, 'normalized:', rotationNormalized);
598
519
 
599
520
  if (rotationNormalized === 0) {
521
+ console.log('[FullDocScanner] Normalized to 0, returning original image');
600
522
  onResult({
601
523
  path: croppedImageData.path,
602
524
  base64: croppedImageData.base64,
603
525
  });
526
+ setProcessing(false);
604
527
  return;
605
528
  }
606
529
 
530
+ // expo-image-manipulator 시도
607
531
  if (isExpoImageManipulatorAvailable() && ImageManipulator?.manipulateAsync) {
608
532
  try {
609
- // expo-image-manipulator 이미지 회전
533
+ console.log('[FullDocScanner] Using expo-image-manipulator for rotation');
534
+ const inputUri = ensureFileUri(croppedImageData.path);
535
+
610
536
  const result = await ImageManipulator.manipulateAsync(
611
- croppedImageData.path,
537
+ inputUri,
612
538
  [{ rotate: rotationNormalized }],
613
539
  {
614
540
  compress: 0.9,
@@ -617,10 +543,16 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
617
543
  },
618
544
  );
619
545
 
620
- onResult({
546
+ console.log('[FullDocScanner] Rotation complete via expo-image-manipulator:', {
621
547
  path: result.uri,
548
+ hasBase64: !!result.base64,
549
+ });
550
+
551
+ onResult({
552
+ path: stripFileUri(result.uri),
622
553
  base64: result.base64,
623
554
  });
555
+ setProcessing(false);
624
556
  return;
625
557
  } catch (manipulatorError) {
626
558
  const code =
@@ -628,10 +560,12 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
628
560
  ? String((manipulatorError as any).code)
629
561
  : undefined;
630
562
 
563
+ console.error('[FullDocScanner] expo-image-manipulator error:', manipulatorError);
564
+
631
565
  if (code === 'ERR_UNAVAILABLE') {
632
566
  expoManipulatorUnavailable = true;
633
567
  console.warn(
634
- '[FullDocScanner] expo-image-manipulator unavailable at runtime. Falling back to react-native-image-rotate if possible.',
568
+ '[FullDocScanner] expo-image-manipulator unavailable at runtime. Trying react-native-image-rotate.',
635
569
  );
636
570
  } else {
637
571
  throw manipulatorError;
@@ -639,48 +573,45 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
639
573
  }
640
574
  }
641
575
 
642
- if (isImageRotateAvailable && ImageRotate?.rotateImage) {
643
- 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
-
649
- try {
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;
656
- } catch (readError) {
657
- console.warn('[FullDocScanner] Failed to persist rotated image from ImageStore:', readError);
658
- }
576
+ // expo-image-manipulator를 사용할 없는 경우 에러 처리
577
+ console.error(
578
+ '[FullDocScanner] Rotation requested but expo-image-manipulator is not available.',
579
+ );
580
+ setProcessing(false);
581
+ Alert.alert(
582
+ '회전 불가',
583
+ 'expo-image-manipulator가 설치되지 않아 이미지 회전을 수행할 수 없습니다.\n\n패키지를 설치해주세요:\nnpm install expo-image-manipulator',
584
+ [
585
+ {
586
+ text: '원본 사용',
587
+ onPress: () => {
588
+ onResult({
589
+ path: croppedImageData.path,
590
+ base64: croppedImageData.base64,
591
+ });
592
+ },
593
+ },
594
+ {
595
+ text: '취소',
596
+ style: 'cancel',
597
+ },
598
+ ],
599
+ );
600
+ } catch (error) {
601
+ console.error('[FullDocScanner] Image rotation error on confirm:', error);
602
+ setProcessing(false);
659
603
 
660
- onResult({
661
- path: finalPath,
662
- base64: base64Result,
663
- });
664
- return;
665
- }
604
+ const errorMessage =
605
+ error && typeof error === 'object' && 'message' in error ? (error as Error).message : String(error);
606
+ Alert.alert('회전 실패', `이미지 회전 중 오류가 발생했습니다.\n원본 이미지를 사용합니다.\n\n오류: ${errorMessage}`);
666
607
 
667
- console.warn(
668
- '[FullDocScanner] Rotation requested but no supported rotation module is available. Returning original image.',
669
- );
608
+ // 에러 발생 시 원본 이미지 반환
670
609
  onResult({
671
610
  path: croppedImageData.path,
672
611
  base64: croppedImageData.base64,
673
612
  });
674
- } catch (error) {
675
- console.error('[FullDocScanner] Image rotation error on confirm:', error);
676
-
677
- const errorMessage =
678
- error && typeof error === 'object' && 'message' in error ? (error as Error).message : '';
679
- Alert.alert('회전 실패', '이미지 회전 중 오류가 발생했습니다: ' + errorMessage);
680
- } finally {
681
- setProcessing(false);
682
613
  }
683
- }, [croppedImageData, rotationDegrees, onResult, rotateImageWithFallback]);
614
+ }, [croppedImageData, rotationDegrees, onResult]);
684
615
 
685
616
  const handleRetake = useCallback(() => {
686
617
  console.log('[FullDocScanner] Retake - clearing cropped image and resetting scanner');