react-native-rectangle-doc-scanner 3.27.0 → 3.31.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/CropEditor.js +11 -6
- package/dist/DocScanner.d.ts +3 -0
- package/dist/DocScanner.js +45 -17
- package/dist/FullDocScanner.js +201 -38
- package/dist/types.d.ts +1 -0
- package/dist/utils/overlay.js +25 -105
- package/package.json +1 -1
- package/src/CropEditor.tsx +15 -8
- package/src/DocScanner.tsx +52 -17
- package/src/FullDocScanner.tsx +288 -55
- package/src/types.ts +1 -0
- package/src/utils/overlay.tsx +39 -158
package/src/utils/overlay.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
3
|
import type { Rectangle } from '../types';
|
|
4
4
|
|
|
5
5
|
let SvgModule: typeof import('react-native-svg') | null = null;
|
|
@@ -11,19 +11,35 @@ try {
|
|
|
11
11
|
SvgModule = null;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const SCAN_DURATION_MS = 2200;
|
|
15
14
|
const GRID_STEPS = [1 / 3, 2 / 3];
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
const createPointsString = (polygon: Rectangle): string =>
|
|
17
|
+
[
|
|
18
|
+
`${polygon.topLeft.x},${polygon.topLeft.y}`,
|
|
19
|
+
`${polygon.topRight.x},${polygon.topRight.y}`,
|
|
20
|
+
`${polygon.bottomRight.x},${polygon.bottomRight.y}`,
|
|
21
|
+
`${polygon.bottomLeft.x},${polygon.bottomLeft.y}`,
|
|
22
|
+
].join(' ');
|
|
23
|
+
|
|
24
|
+
const interpolatePoint = (a: { x: number; y: number }, b: { x: number; y: number }, t: number) => ({
|
|
25
|
+
x: a.x + (b.x - a.x) * t,
|
|
26
|
+
y: a.y + (b.y - a.y) * t,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const createGridLines = (polygon: Rectangle) =>
|
|
30
|
+
GRID_STEPS.flatMap((step) => {
|
|
31
|
+
const horizontalStart = interpolatePoint(polygon.topLeft, polygon.bottomLeft, step);
|
|
32
|
+
const horizontalEnd = interpolatePoint(polygon.topRight, polygon.bottomRight, step);
|
|
33
|
+
const verticalStart = interpolatePoint(polygon.topLeft, polygon.topRight, step);
|
|
34
|
+
const verticalEnd = interpolatePoint(polygon.bottomLeft, polygon.bottomRight, step);
|
|
35
|
+
|
|
36
|
+
return [
|
|
37
|
+
{ x1: horizontalStart.x, y1: horizontalStart.y, x2: horizontalEnd.x, y2: horizontalEnd.y },
|
|
38
|
+
{ x1: verticalStart.x, y1: verticalStart.y, x2: verticalEnd.x, y2: verticalEnd.y },
|
|
39
|
+
];
|
|
40
|
+
});
|
|
25
41
|
|
|
26
|
-
const
|
|
42
|
+
const getBounds = (polygon: Rectangle) => {
|
|
27
43
|
const minX = Math.min(
|
|
28
44
|
polygon.topLeft.x,
|
|
29
45
|
polygon.bottomLeft.x,
|
|
@@ -51,40 +67,12 @@ const calculateMetrics = (polygon: Rectangle): PolygonMetrics => {
|
|
|
51
67
|
|
|
52
68
|
return {
|
|
53
69
|
minX,
|
|
54
|
-
maxX,
|
|
55
70
|
minY,
|
|
56
|
-
maxY,
|
|
57
71
|
width: maxX - minX,
|
|
58
72
|
height: maxY - minY,
|
|
59
73
|
};
|
|
60
74
|
};
|
|
61
75
|
|
|
62
|
-
const createPointsString = (polygon: Rectangle): string =>
|
|
63
|
-
[
|
|
64
|
-
`${polygon.topLeft.x},${polygon.topLeft.y}`,
|
|
65
|
-
`${polygon.topRight.x},${polygon.topRight.y}`,
|
|
66
|
-
`${polygon.bottomRight.x},${polygon.bottomRight.y}`,
|
|
67
|
-
`${polygon.bottomLeft.x},${polygon.bottomLeft.y}`,
|
|
68
|
-
].join(' ');
|
|
69
|
-
|
|
70
|
-
const interpolatePoint = (a: { x: number; y: number }, b: { x: number; y: number }, t: number) => ({
|
|
71
|
-
x: a.x + (b.x - a.x) * t,
|
|
72
|
-
y: a.y + (b.y - a.y) * t,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const createGridLines = (polygon: Rectangle) =>
|
|
76
|
-
GRID_STEPS.flatMap((step) => {
|
|
77
|
-
const horizontalStart = interpolatePoint(polygon.topLeft, polygon.bottomLeft, step);
|
|
78
|
-
const horizontalEnd = interpolatePoint(polygon.topRight, polygon.bottomRight, step);
|
|
79
|
-
const verticalStart = interpolatePoint(polygon.topLeft, polygon.topRight, step);
|
|
80
|
-
const verticalEnd = interpolatePoint(polygon.bottomLeft, polygon.bottomRight, step);
|
|
81
|
-
|
|
82
|
-
return [
|
|
83
|
-
{ x1: horizontalStart.x, y1: horizontalStart.y, x2: horizontalEnd.x, y2: horizontalEnd.y },
|
|
84
|
-
{ x1: verticalStart.x, y1: verticalStart.y, x2: verticalEnd.x, y2: verticalEnd.y },
|
|
85
|
-
];
|
|
86
|
-
});
|
|
87
|
-
|
|
88
76
|
export interface ScannerOverlayProps {
|
|
89
77
|
active: boolean;
|
|
90
78
|
color?: string;
|
|
@@ -93,89 +81,21 @@ export interface ScannerOverlayProps {
|
|
|
93
81
|
}
|
|
94
82
|
|
|
95
83
|
export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
|
|
96
|
-
active,
|
|
84
|
+
active: _active, // kept for compatibility; no animation currently
|
|
97
85
|
color = '#0b7ef4',
|
|
98
86
|
lineWidth = StyleSheet.hairlineWidth,
|
|
99
87
|
polygon,
|
|
100
88
|
}) => {
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const metrics = useMemo(() => (polygon ? calculateMetrics(polygon) : null), [polygon]);
|
|
106
|
-
|
|
107
|
-
const scanBarHeight = useMemo(() => {
|
|
108
|
-
if (!metrics) return 0;
|
|
109
|
-
return Math.max(metrics.height * 0.2, 16);
|
|
110
|
-
}, [metrics]);
|
|
111
|
-
|
|
112
|
-
const travelDistance = useMemo(() => {
|
|
113
|
-
if (!metrics) {
|
|
114
|
-
return 0;
|
|
115
|
-
}
|
|
116
|
-
return Math.max(metrics.height - scanBarHeight, 0);
|
|
117
|
-
}, [metrics, scanBarHeight]);
|
|
118
|
-
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
scanProgress.stopAnimation();
|
|
121
|
-
scanProgress.setValue(0);
|
|
122
|
-
setScanY(null);
|
|
123
|
-
|
|
124
|
-
if (!active || !metrics || travelDistance <= 0) {
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const loop = Animated.loop(
|
|
129
|
-
Animated.sequence([
|
|
130
|
-
Animated.timing(scanProgress, {
|
|
131
|
-
toValue: 1,
|
|
132
|
-
duration: SCAN_DURATION_MS,
|
|
133
|
-
easing: Easing.inOut(Easing.quad),
|
|
134
|
-
useNativeDriver: false,
|
|
135
|
-
}),
|
|
136
|
-
Animated.timing(scanProgress, {
|
|
137
|
-
toValue: 0,
|
|
138
|
-
duration: SCAN_DURATION_MS,
|
|
139
|
-
easing: Easing.inOut(Easing.quad),
|
|
140
|
-
useNativeDriver: false,
|
|
141
|
-
}),
|
|
142
|
-
]),
|
|
143
|
-
);
|
|
89
|
+
const points = useMemo(() => (polygon ? createPointsString(polygon) : null), [polygon]);
|
|
90
|
+
const gridLines = useMemo(() => (polygon ? createGridLines(polygon) : []), [polygon]);
|
|
91
|
+
const bounds = useMemo(() => (polygon ? getBounds(polygon) : null), [polygon]);
|
|
144
92
|
|
|
145
|
-
|
|
146
|
-
return () => {
|
|
147
|
-
loop.stop();
|
|
148
|
-
scanProgress.stopAnimation();
|
|
149
|
-
};
|
|
150
|
-
}, [active, metrics, scanProgress, travelDistance]);
|
|
151
|
-
|
|
152
|
-
useEffect(() => {
|
|
153
|
-
if (!metrics || travelDistance <= 0) {
|
|
154
|
-
setScanY(null);
|
|
155
|
-
return undefined;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const listenerId = scanProgress.addListener(({ value }) => {
|
|
159
|
-
const nextValue = metrics.minY + travelDistance * value;
|
|
160
|
-
if (Number.isFinite(nextValue)) {
|
|
161
|
-
setScanY(nextValue);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
return () => {
|
|
166
|
-
scanProgress.removeListener(listenerId);
|
|
167
|
-
};
|
|
168
|
-
}, [metrics, scanProgress, travelDistance]);
|
|
169
|
-
|
|
170
|
-
if (!polygon || !metrics || metrics.width <= 0 || metrics.height <= 0) {
|
|
93
|
+
if (!polygon || !points || !bounds) {
|
|
171
94
|
return null;
|
|
172
95
|
}
|
|
173
96
|
|
|
174
97
|
if (SvgModule) {
|
|
175
|
-
const { default: Svg, Polygon, Line
|
|
176
|
-
const gridLines = createGridLines(polygon);
|
|
177
|
-
const points = createPointsString(polygon);
|
|
178
|
-
const scanRectY = scanY ?? metrics.minY;
|
|
98
|
+
const { default: Svg, Polygon, Line } = SvgModule;
|
|
179
99
|
|
|
180
100
|
return (
|
|
181
101
|
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
|
@@ -194,60 +114,26 @@ export const ScannerOverlay: React.FC<ScannerOverlayProps> = ({
|
|
|
194
114
|
/>
|
|
195
115
|
))}
|
|
196
116
|
<Polygon points={points} stroke={color} strokeWidth={lineWidth} fill="none" />
|
|
197
|
-
<Defs>
|
|
198
|
-
<LinearGradient id="scanGradient" x1="0" y1="0" x2="0" y2="1">
|
|
199
|
-
<Stop offset="0%" stopColor="rgba(255,255,255,0)" />
|
|
200
|
-
<Stop offset="50%" stopColor={color} stopOpacity={0.8} />
|
|
201
|
-
<Stop offset="100%" stopColor="rgba(255,255,255,0)" />
|
|
202
|
-
</LinearGradient>
|
|
203
|
-
</Defs>
|
|
204
|
-
{active && travelDistance > 0 && Number.isFinite(scanRectY) && (
|
|
205
|
-
<Rect
|
|
206
|
-
x={metrics.minX}
|
|
207
|
-
width={metrics.width}
|
|
208
|
-
height={scanBarHeight}
|
|
209
|
-
fill="url(#scanGradient)"
|
|
210
|
-
y={scanRectY}
|
|
211
|
-
/>
|
|
212
|
-
)}
|
|
213
117
|
</Svg>
|
|
214
118
|
</View>
|
|
215
119
|
);
|
|
216
120
|
}
|
|
217
121
|
|
|
218
|
-
const relativeTranslate =
|
|
219
|
-
metrics && travelDistance > 0
|
|
220
|
-
? Animated.multiply(scanProgress, travelDistance)
|
|
221
|
-
: fallbackBase;
|
|
222
|
-
|
|
223
122
|
return (
|
|
224
123
|
<View pointerEvents="none" style={StyleSheet.absoluteFill}>
|
|
225
124
|
<View
|
|
226
125
|
style={[
|
|
227
126
|
styles.fallbackBox,
|
|
228
127
|
{
|
|
229
|
-
left:
|
|
230
|
-
top:
|
|
231
|
-
width:
|
|
232
|
-
height:
|
|
128
|
+
left: bounds.minX,
|
|
129
|
+
top: bounds.minY,
|
|
130
|
+
width: bounds.width,
|
|
131
|
+
height: bounds.height,
|
|
233
132
|
borderColor: color,
|
|
234
133
|
borderWidth: lineWidth,
|
|
235
134
|
},
|
|
236
135
|
]}
|
|
237
|
-
|
|
238
|
-
{active && travelDistance > 0 && (
|
|
239
|
-
<Animated.View
|
|
240
|
-
style={[
|
|
241
|
-
styles.fallbackScanBar,
|
|
242
|
-
{
|
|
243
|
-
backgroundColor: color,
|
|
244
|
-
height: scanBarHeight,
|
|
245
|
-
transform: [{ translateY: relativeTranslate }],
|
|
246
|
-
},
|
|
247
|
-
]}
|
|
248
|
-
/>
|
|
249
|
-
)}
|
|
250
|
-
</View>
|
|
136
|
+
/>
|
|
251
137
|
</View>
|
|
252
138
|
);
|
|
253
139
|
};
|
|
@@ -256,10 +142,5 @@ const styles = StyleSheet.create({
|
|
|
256
142
|
fallbackBox: {
|
|
257
143
|
position: 'absolute',
|
|
258
144
|
backgroundColor: 'rgba(11, 126, 244, 0.1)',
|
|
259
|
-
overflow: 'hidden',
|
|
260
|
-
},
|
|
261
|
-
fallbackScanBar: {
|
|
262
|
-
width: '100%',
|
|
263
|
-
opacity: 0.4,
|
|
264
145
|
},
|
|
265
146
|
});
|