react-native-rectangle-doc-scanner 3.37.0 → 3.39.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.
@@ -32,14 +32,10 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
35
  Object.defineProperty(exports, "__esModule", { value: true });
39
36
  exports.CropEditor = void 0;
40
37
  const react_1 = __importStar(require("react"));
41
38
  const react_native_1 = require("react-native");
42
- const react_native_perspective_image_cropper_1 = __importDefault(require("react-native-perspective-image-cropper"));
43
39
  const coordinate_1 = require("./utils/coordinate");
44
40
  /**
45
41
  * CropEditor Component
@@ -62,17 +58,25 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
62
58
  });
63
59
  const [isImageLoading, setIsImageLoading] = (0, react_1.useState)(true);
64
60
  const [loadError, setLoadError] = (0, react_1.useState)(null);
61
+ const [croppedImageUri, setCroppedImageUri] = (0, react_1.useState)(null);
65
62
  (0, react_1.useEffect)(() => {
66
63
  console.log('[CropEditor] Document path:', document.path);
67
64
  console.log('[CropEditor] Document dimensions:', document.width, 'x', document.height);
68
65
  console.log('[CropEditor] Document quad:', document.quad);
66
+ console.log('[CropEditor] Document rectangle:', document.rectangle);
69
67
  // Load image size using Image.getSize
70
- const imageUri = `file://${document.path}`;
68
+ const imageUri = document.path.startsWith('file://') ? document.path : `file://${document.path}`;
71
69
  react_native_1.Image.getSize(imageUri, (width, height) => {
72
70
  console.log('[CropEditor] Image.getSize success:', { width, height });
73
71
  setImageSize({ width, height });
74
- setIsImageLoading(false);
75
- setLoadError(null);
72
+ // If we have a rectangle (from auto-capture), crop the image
73
+ if (document.rectangle || document.quad) {
74
+ cropImageToRectangle(imageUri, width, height);
75
+ }
76
+ else {
77
+ setIsImageLoading(false);
78
+ setLoadError(null);
79
+ }
76
80
  }, (error) => {
77
81
  console.error('[CropEditor] Image.getSize error:', error);
78
82
  // Fallback to document dimensions
@@ -81,6 +85,48 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
81
85
  setIsImageLoading(false);
82
86
  });
83
87
  }, [document]);
88
+ const cropImageToRectangle = (0, react_1.useCallback)((imageUri, width, height) => {
89
+ const cropManager = react_native_1.NativeModules.CustomCropManager;
90
+ if (!cropManager?.crop) {
91
+ console.warn('[CropEditor] CustomCropManager not available, showing original image');
92
+ setIsImageLoading(false);
93
+ return;
94
+ }
95
+ const baseWidth = document.width > 0 ? document.width : width;
96
+ const baseHeight = document.height > 0 ? document.height : height;
97
+ let rectangle = document.rectangle ?? null;
98
+ if (!rectangle && document.quad && document.quad.length === 4) {
99
+ rectangle = (0, coordinate_1.quadToRectangle)(document.quad);
100
+ }
101
+ if (!rectangle) {
102
+ console.warn('[CropEditor] No rectangle found, showing original image');
103
+ setIsImageLoading(false);
104
+ return;
105
+ }
106
+ // Scale rectangle to actual image size
107
+ const scaledRect = (0, coordinate_1.scaleRectangle)(rectangle, baseWidth, baseHeight, width, height);
108
+ console.log('[CropEditor] Cropping image with rectangle:', scaledRect);
109
+ cropManager.crop({
110
+ topLeft: scaledRect.topLeft,
111
+ topRight: scaledRect.topRight,
112
+ bottomRight: scaledRect.bottomRight,
113
+ bottomLeft: scaledRect.bottomLeft,
114
+ width,
115
+ height,
116
+ }, imageUri, (error, result) => {
117
+ if (error) {
118
+ console.error('[CropEditor] Crop error:', error);
119
+ setIsImageLoading(false);
120
+ return;
121
+ }
122
+ console.log('[CropEditor] Crop success, base64 length:', result.image?.length);
123
+ // Convert base64 to data URI
124
+ const croppedUri = `data:image/jpeg;base64,${result.image}`;
125
+ setCroppedImageUri(croppedUri);
126
+ setIsImageLoading(false);
127
+ setLoadError(null);
128
+ });
129
+ }, [document]);
84
130
  // Get initial rectangle from detected quad or use default
85
131
  const getInitialRectangle = (0, react_1.useCallback)(() => {
86
132
  if (!imageSize) {
@@ -135,8 +181,7 @@ const CropEditor = ({ document, overlayColor = 'rgba(0,0,0,0.5)', overlayStrokeC
135
181
  react_1.default.createElement(react_native_1.Text, { style: styles.errorPath }, imageUri))) : !imageSize || isImageLoading ? (react_1.default.createElement(react_native_1.View, { style: styles.loadingContainer },
136
182
  react_1.default.createElement(react_native_1.ActivityIndicator, { size: "large", color: handlerColor }),
137
183
  react_1.default.createElement(react_native_1.Text, { style: styles.loadingText }, "Loading image..."))) : (react_1.default.createElement(react_1.default.Fragment, null,
138
- react_1.default.createElement(react_native_perspective_image_cropper_1.default, { height: displaySize.height, width: displaySize.width, image: imageUri, rectangleCoordinates: initialRect, overlayColor: overlayColor, overlayStrokeColor: overlayStrokeColor, handlerColor: handlerColor, enablePanStrict: enablePanStrict, onDragEnd: handleDragEnd }),
139
- react_1.default.createElement(react_native_1.Image, { source: { uri: imageUri }, style: styles.debugImage, onLoad: () => console.log('[CropEditor] Debug image loaded'), onError: (e) => console.error('[CropEditor] Debug image error:', e.nativeEvent.error) })))));
184
+ react_1.default.createElement(react_native_1.Image, { source: { uri: croppedImageUri || imageUri }, style: styles.fullImage, resizeMode: "contain", onLoad: () => console.log('[CropEditor] Image loaded successfully', croppedImageUri ? 'cropped' : 'original'), onError: (e) => console.error('[CropEditor] Image load error:', e.nativeEvent.error) })))));
140
185
  };
141
186
  exports.CropEditor = CropEditor;
142
187
  const styles = react_native_1.StyleSheet.create({
@@ -171,14 +216,8 @@ const styles = react_native_1.StyleSheet.create({
171
216
  fontSize: 12,
172
217
  textAlign: 'center',
173
218
  },
174
- debugImage: {
175
- position: 'absolute',
176
- width: 100,
177
- height: 100,
178
- top: 10,
179
- right: 10,
180
- opacity: 0.5,
181
- borderWidth: 2,
182
- borderColor: 'red',
219
+ fullImage: {
220
+ width: '100%',
221
+ height: '100%',
183
222
  },
184
223
  });
@@ -224,12 +224,20 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
224
224
  console.log('[FullDocScanner] Already processing, skipping manual capture');
225
225
  return;
226
226
  }
227
+ if (manualCapturePending.current) {
228
+ console.log('[FullDocScanner] Manual capture already pending, skipping');
229
+ return;
230
+ }
227
231
  console.log('[FullDocScanner] Setting manualCapturePending to true');
228
232
  manualCapturePending.current = true;
229
233
  const capturePromise = docScannerRef.current?.capture();
230
234
  console.log('[FullDocScanner] capturePromise:', !!capturePromise);
231
- if (capturePromise && typeof capturePromise.catch === 'function') {
232
- capturePromise.catch((error) => {
235
+ if (capturePromise && typeof capturePromise.then === 'function') {
236
+ capturePromise
237
+ .then(() => {
238
+ console.log('[FullDocScanner] Capture success');
239
+ })
240
+ .catch((error) => {
233
241
  manualCapturePending.current = false;
234
242
  console.warn('[FullDocScanner] manual capture failed', error);
235
243
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "3.37.0",
3
+ "version": "3.39.0",
4
4
  "description": "Native-backed document scanner for React Native with customizable overlays.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,10 +1,25 @@
1
1
  import React, { useState, useCallback, useEffect } from 'react';
2
- import { View, StyleSheet, Image, Dimensions, ActivityIndicator, Text } from 'react-native';
2
+ import { View, StyleSheet, Image, Dimensions, ActivityIndicator, Text, NativeModules } from 'react-native';
3
3
  import CustomImageCropper from 'react-native-perspective-image-cropper';
4
4
  import type { Rectangle as CropperRectangle } from 'react-native-perspective-image-cropper';
5
5
  import type { Point, Rectangle, CapturedDocument } from './types';
6
6
  import { createFullImageRectangle, quadToRectangle, scaleRectangle } from './utils/coordinate';
7
7
 
8
+ type CustomCropManagerType = {
9
+ crop: (
10
+ points: {
11
+ topLeft: Point;
12
+ topRight: Point;
13
+ bottomRight: Point;
14
+ bottomLeft: Point;
15
+ width: number;
16
+ height: number;
17
+ },
18
+ imageUri: string,
19
+ callback: (error: unknown, result: { image: string }) => void,
20
+ ) => void;
21
+ };
22
+
8
23
  interface CropEditorProps {
9
24
  document: CapturedDocument;
10
25
  overlayColor?: string;
@@ -42,21 +57,29 @@ export const CropEditor: React.FC<CropEditorProps> = ({
42
57
  });
43
58
  const [isImageLoading, setIsImageLoading] = useState(true);
44
59
  const [loadError, setLoadError] = useState<string | null>(null);
60
+ const [croppedImageUri, setCroppedImageUri] = useState<string | null>(null);
45
61
 
46
62
  useEffect(() => {
47
63
  console.log('[CropEditor] Document path:', document.path);
48
64
  console.log('[CropEditor] Document dimensions:', document.width, 'x', document.height);
49
65
  console.log('[CropEditor] Document quad:', document.quad);
66
+ console.log('[CropEditor] Document rectangle:', document.rectangle);
50
67
 
51
68
  // Load image size using Image.getSize
52
- const imageUri = `file://${document.path}`;
69
+ const imageUri = document.path.startsWith('file://') ? document.path : `file://${document.path}`;
53
70
  Image.getSize(
54
71
  imageUri,
55
72
  (width, height) => {
56
73
  console.log('[CropEditor] Image.getSize success:', { width, height });
57
74
  setImageSize({ width, height });
58
- setIsImageLoading(false);
59
- setLoadError(null);
75
+
76
+ // If we have a rectangle (from auto-capture), crop the image
77
+ if (document.rectangle || document.quad) {
78
+ cropImageToRectangle(imageUri, width, height);
79
+ } else {
80
+ setIsImageLoading(false);
81
+ setLoadError(null);
82
+ }
60
83
  },
61
84
  (error) => {
62
85
  console.error('[CropEditor] Image.getSize error:', error);
@@ -68,6 +91,60 @@ export const CropEditor: React.FC<CropEditorProps> = ({
68
91
  );
69
92
  }, [document]);
70
93
 
94
+ const cropImageToRectangle = useCallback((imageUri: string, width: number, height: number) => {
95
+ const cropManager = NativeModules.CustomCropManager as CustomCropManagerType | undefined;
96
+
97
+ if (!cropManager?.crop) {
98
+ console.warn('[CropEditor] CustomCropManager not available, showing original image');
99
+ setIsImageLoading(false);
100
+ return;
101
+ }
102
+
103
+ const baseWidth = document.width > 0 ? document.width : width;
104
+ const baseHeight = document.height > 0 ? document.height : height;
105
+
106
+ let rectangle: Rectangle | null = document.rectangle ?? null;
107
+ if (!rectangle && document.quad && document.quad.length === 4) {
108
+ rectangle = quadToRectangle(document.quad);
109
+ }
110
+
111
+ if (!rectangle) {
112
+ console.warn('[CropEditor] No rectangle found, showing original image');
113
+ setIsImageLoading(false);
114
+ return;
115
+ }
116
+
117
+ // Scale rectangle to actual image size
118
+ const scaledRect = scaleRectangle(rectangle, baseWidth, baseHeight, width, height);
119
+
120
+ console.log('[CropEditor] Cropping image with rectangle:', scaledRect);
121
+
122
+ cropManager.crop(
123
+ {
124
+ topLeft: scaledRect.topLeft,
125
+ topRight: scaledRect.topRight,
126
+ bottomRight: scaledRect.bottomRight,
127
+ bottomLeft: scaledRect.bottomLeft,
128
+ width,
129
+ height,
130
+ },
131
+ imageUri,
132
+ (error: unknown, result: { image: string }) => {
133
+ if (error) {
134
+ console.error('[CropEditor] Crop error:', error);
135
+ setIsImageLoading(false);
136
+ return;
137
+ }
138
+ console.log('[CropEditor] Crop success, base64 length:', result.image?.length);
139
+ // Convert base64 to data URI
140
+ const croppedUri = `data:image/jpeg;base64,${result.image}`;
141
+ setCroppedImageUri(croppedUri);
142
+ setIsImageLoading(false);
143
+ setLoadError(null);
144
+ }
145
+ );
146
+ }, [document]);
147
+
71
148
  // Get initial rectangle from detected quad or use default
72
149
  const getInitialRectangle = useCallback((): CropperRectangle | undefined => {
73
150
  if (!imageSize) {
@@ -151,7 +228,16 @@ export const CropEditor: React.FC<CropEditorProps> = ({
151
228
  </View>
152
229
  ) : (
153
230
  <>
154
- <CustomImageCropper
231
+ {/* Show cropped image if available, otherwise show original */}
232
+ <Image
233
+ source={{ uri: croppedImageUri || imageUri }}
234
+ style={styles.fullImage}
235
+ resizeMode="contain"
236
+ onLoad={() => console.log('[CropEditor] Image loaded successfully', croppedImageUri ? 'cropped' : 'original')}
237
+ onError={(e) => console.error('[CropEditor] Image load error:', e.nativeEvent.error)}
238
+ />
239
+ {/* Temporarily disabled CustomImageCropper - showing image only */}
240
+ {/* <CustomImageCropper
155
241
  height={displaySize.height}
156
242
  width={displaySize.width}
157
243
  image={imageUri}
@@ -161,14 +247,7 @@ export const CropEditor: React.FC<CropEditorProps> = ({
161
247
  handlerColor={handlerColor}
162
248
  enablePanStrict={enablePanStrict}
163
249
  onDragEnd={handleDragEnd}
164
- />
165
- {/* Debug: Show image in background to verify it loads */}
166
- <Image
167
- source={{ uri: imageUri }}
168
- style={styles.debugImage}
169
- onLoad={() => console.log('[CropEditor] Debug image loaded')}
170
- onError={(e) => console.error('[CropEditor] Debug image error:', e.nativeEvent.error)}
171
- />
250
+ /> */}
172
251
  </>
173
252
  )}
174
253
  </View>
@@ -207,14 +286,8 @@ const styles = StyleSheet.create({
207
286
  fontSize: 12,
208
287
  textAlign: 'center',
209
288
  },
210
- debugImage: {
211
- position: 'absolute',
212
- width: 100,
213
- height: 100,
214
- top: 10,
215
- right: 10,
216
- opacity: 0.5,
217
- borderWidth: 2,
218
- borderColor: 'red',
289
+ fullImage: {
290
+ width: '100%',
291
+ height: '100%',
219
292
  },
220
293
  });
@@ -347,15 +347,25 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
347
347
  console.log('[FullDocScanner] Already processing, skipping manual capture');
348
348
  return;
349
349
  }
350
+ if (manualCapturePending.current) {
351
+ console.log('[FullDocScanner] Manual capture already pending, skipping');
352
+ return;
353
+ }
354
+
350
355
  console.log('[FullDocScanner] Setting manualCapturePending to true');
351
356
  manualCapturePending.current = true;
357
+
352
358
  const capturePromise = docScannerRef.current?.capture();
353
359
  console.log('[FullDocScanner] capturePromise:', !!capturePromise);
354
- if (capturePromise && typeof capturePromise.catch === 'function') {
355
- capturePromise.catch((error: unknown) => {
356
- manualCapturePending.current = false;
357
- console.warn('[FullDocScanner] manual capture failed', error);
358
- });
360
+ if (capturePromise && typeof capturePromise.then === 'function') {
361
+ capturePromise
362
+ .then(() => {
363
+ console.log('[FullDocScanner] Capture success');
364
+ })
365
+ .catch((error: unknown) => {
366
+ manualCapturePending.current = false;
367
+ console.warn('[FullDocScanner] manual capture failed', error);
368
+ });
359
369
  } else if (!capturePromise) {
360
370
  console.warn('[FullDocScanner] No capture promise returned');
361
371
  manualCapturePending.current = false;