react-native-rectangle-doc-scanner 0.65.0 → 0.69.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.
@@ -56,6 +56,9 @@ export interface FullDocScannerProps {
56
56
  onClose?: () => void;
57
57
  detectionConfig?: DetectionConfig;
58
58
  overlayColor?: string;
59
+ gridColor?: string;
60
+ gridLineWidth?: number;
61
+ showGrid?: boolean;
59
62
  overlayStrokeColor?: string;
60
63
  handlerColor?: string;
61
64
  strings?: FullDocScannerStrings;
@@ -71,6 +74,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
71
74
  onClose,
72
75
  detectionConfig,
73
76
  overlayColor = '#3170f3',
77
+ gridColor,
78
+ gridLineWidth,
79
+ showGrid,
74
80
  overlayStrokeColor = '#3170f3',
75
81
  handlerColor = '#3170f3',
76
82
  strings,
@@ -83,6 +89,7 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
83
89
  const [cropRectangle, setCropRectangle] = useState<Rectangle | null>(null);
84
90
  const [imageSize, setImageSize] = useState<{ width: number; height: number } | null>(null);
85
91
  const [processing, setProcessing] = useState(false);
92
+ const resolvedGridColor = gridColor ?? overlayColor;
86
93
 
87
94
  const mergedStrings = useMemo<Required<FullDocScannerStrings>>(
88
95
  () => ({
@@ -239,6 +246,9 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
239
246
  <DocScanner
240
247
  autoCapture={!manualCapture}
241
248
  overlayColor={overlayColor}
249
+ showGrid={showGrid}
250
+ gridColor={resolvedGridColor}
251
+ gridLineWidth={gridLineWidth}
242
252
  minStableFrames={minStableFrames ?? 6}
243
253
  detectionConfig={detectionConfig}
244
254
  onCapture={handleCapture}
package/src/external.d.ts CHANGED
@@ -1,48 +1,3 @@
1
- declare module 'react-native-document-scanner-plugin' {
2
- import type { ComponentType } from 'react';
3
- import type { ViewStyle } from 'react-native';
4
-
5
- export type RectangleCoordinates = {
6
- topLeft: { x: number; y: number };
7
- topRight: { x: number; y: number };
8
- bottomRight: { x: number; y: number };
9
- bottomLeft: { x: number; y: number };
10
- };
11
-
12
- export type RectangleEvent = {
13
- rectangleCoordinates?: RectangleCoordinates;
14
- stableCounter?: number;
15
- lastDetectionType?: 'initial' | 'updated' | 'lost';
16
- };
17
-
18
- export type CaptureResult = {
19
- croppedImage?: string;
20
- initialImage?: string;
21
- width?: number;
22
- height?: number;
23
- };
24
-
25
- export type DocumentScannerProps = {
26
- ref?: (value: DocumentScannerHandle | null) => void;
27
- style?: ViewStyle;
28
- overlayColor?: string;
29
- detectionCountBeforeCapture?: number;
30
- enableTorch?: boolean;
31
- hideControls?: boolean;
32
- useBase64?: boolean;
33
- quality?: number;
34
- onRectangleDetect?: (event: RectangleEvent) => void;
35
- onPictureTaken?: (result: CaptureResult) => void;
36
- };
37
-
38
- export type DocumentScannerHandle = {
39
- capture: () => Promise<CaptureResult>;
40
- };
41
-
42
- const DocumentScanner: ComponentType<DocumentScannerProps>;
43
- export default DocumentScanner;
44
- }
45
-
46
1
  declare module '@shopify/react-native-skia' {
47
2
  import type { ComponentType, ReactNode } from 'react';
48
3
  import type { ViewStyle } from 'react-native';
@@ -76,6 +31,18 @@ declare module '@shopify/react-native-skia' {
76
31
  export const Path: ComponentType<PathProps>;
77
32
  }
78
33
 
34
+ declare module 'react-native-rectangle-doc-scanner/RNRDocScannerModule' {
35
+ export type NativeCaptureResult = {
36
+ croppedImage?: string | null;
37
+ initialImage?: string;
38
+ width?: number;
39
+ height?: number;
40
+ };
41
+
42
+ export function capture(viewTag: number): Promise<NativeCaptureResult>;
43
+ export function reset(viewTag: number): void;
44
+ }
45
+
79
46
  declare module 'react-native-perspective-image-cropper' {
80
47
  import type { ComponentType } from 'react';
81
48
 
package/src/index.ts CHANGED
@@ -11,7 +11,7 @@ export type {
11
11
 
12
12
  // Types
13
13
  export type { Point, Quad, Rectangle, CapturedDocument } from './types';
14
- export type { DetectionConfig } from './DocScanner';
14
+ export type { DetectionConfig, DocScannerHandle } from './DocScanner';
15
15
 
16
16
  // Utilities
17
17
  export {
@@ -8,6 +8,23 @@ const lerp = (start: Point, end: Point, t: number): Point => ({
8
8
  y: start.y + (end.y - start.y) * t,
9
9
  });
10
10
 
11
+ const withAlpha = (value: string, alpha: number): string => {
12
+ const hexMatch = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(value.trim());
13
+ if (!hexMatch) {
14
+ return `rgba(231, 166, 73, ${alpha})`;
15
+ }
16
+
17
+ const hex = hexMatch[1];
18
+ const normalize = hex.length === 3
19
+ ? hex.split('').map((ch) => ch + ch).join('')
20
+ : hex;
21
+
22
+ const r = parseInt(normalize.slice(0, 2), 16);
23
+ const g = parseInt(normalize.slice(2, 4), 16);
24
+ const b = parseInt(normalize.slice(4, 6), 16);
25
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
26
+ };
27
+
11
28
  type OverlayProps = {
12
29
  quad: Point[] | null;
13
30
  color?: string;
@@ -22,6 +39,14 @@ type OverlayGeometry = {
22
39
  gridPaths: ReturnType<typeof Skia.Path.Make>[];
23
40
  };
24
41
 
42
+ const buildPath = (points: Point[]) => {
43
+ const path = Skia.Path.Make();
44
+ path.moveTo(points[0].x, points[0].y);
45
+ points.slice(1).forEach((p) => path.lineTo(p.x, p.y));
46
+ path.close();
47
+ return path;
48
+ };
49
+
25
50
  export const Overlay: React.FC<OverlayProps> = ({
26
51
  quad,
27
52
  color = '#e7a649',
@@ -31,71 +56,75 @@ export const Overlay: React.FC<OverlayProps> = ({
31
56
  gridLineWidth = 2,
32
57
  }) => {
33
58
  const { width: screenWidth, height: screenHeight } = useWindowDimensions();
59
+ const fillColor = useMemo(() => withAlpha(color, 0.2), [color]);
34
60
 
35
61
  const { outlinePath, gridPaths }: OverlayGeometry = useMemo(() => {
36
- if (!quad || !frameSize) {
37
- if (__DEV__) {
38
- console.log('[Overlay] no quad or frameSize', { quad, frameSize });
39
- }
40
- return { outlinePath: null, gridPaths: [] };
41
- }
62
+ let transformedQuad: Point[] | null = null;
63
+ let sourceQuad: Point[] | null = null;
64
+ let sourceFrameSize = frameSize;
42
65
 
43
- if (__DEV__) {
44
- console.log('[Overlay] drawing quad:', quad);
45
- console.log('[Overlay] color:', color);
46
- console.log('[Overlay] screen dimensions:', screenWidth, 'x', screenHeight);
47
- console.log('[Overlay] frame dimensions:', frameSize.width, 'x', frameSize.height);
48
- }
49
-
50
- // Check if camera is in landscape mode (width > height) but screen is portrait (height > width)
51
- const isFrameLandscape = frameSize.width > frameSize.height;
52
- const isScreenPortrait = screenHeight > screenWidth;
53
- const needsRotation = isFrameLandscape && isScreenPortrait;
54
-
55
- if (__DEV__) {
56
- console.log('[Overlay] needs rotation:', needsRotation);
66
+ if (quad && frameSize) {
67
+ sourceQuad = quad;
68
+ } else {
69
+ const marginRatio = 0.12;
70
+ const marginX = screenWidth * marginRatio;
71
+ const marginY = screenHeight * marginRatio;
72
+ const maxWidth = screenWidth - marginX * 2;
73
+ const maxHeight = screenHeight - marginY * 2;
74
+ const a4Ratio = Math.SQRT2; // ~1.414 height / width
75
+ let width = maxWidth;
76
+ let height = width * a4Ratio;
77
+ if (height > maxHeight) {
78
+ height = maxHeight;
79
+ width = height / a4Ratio;
80
+ }
81
+ const left = (screenWidth - width) / 2;
82
+ const top = (screenHeight - height) / 2;
83
+ transformedQuad = [
84
+ { x: left, y: top },
85
+ { x: left + width, y: top },
86
+ { x: left + width, y: top + height },
87
+ { x: left, y: top + height },
88
+ ];
89
+ sourceFrameSize = null;
57
90
  }
58
91
 
59
- let transformedQuad: Point[];
60
-
61
- if (needsRotation) {
62
- // Camera is landscape, screen is portrait - need to rotate 90 degrees
63
- // Transform: rotate 90° clockwise and scale
64
- // New coordinates: x' = y * (screenWidth / frameHeight), y' = (frameWidth - x) * (screenHeight / frameWidth)
65
- const scaleX = screenWidth / frameSize.height;
66
- const scaleY = screenHeight / frameSize.width;
67
-
92
+ if (sourceQuad && sourceFrameSize) {
68
93
  if (__DEV__) {
69
- console.log('[Overlay] rotation scale factors:', scaleX, 'x', scaleY);
94
+ console.log('[Overlay] drawing quad:', sourceQuad);
95
+ console.log('[Overlay] color:', color);
96
+ console.log('[Overlay] screen dimensions:', screenWidth, 'x', screenHeight);
97
+ console.log('[Overlay] frame dimensions:', sourceFrameSize.width, 'x', sourceFrameSize.height);
70
98
  }
71
99
 
72
- transformedQuad = quad.map((p) => ({
73
- x: p.y * scaleX,
74
- y: (frameSize.width - p.x) * scaleY,
75
- }));
76
- } else {
77
- // Same orientation - just scale
78
- const scaleX = screenWidth / frameSize.width;
79
- const scaleY = screenHeight / frameSize.height;
80
-
81
- if (__DEV__) {
82
- console.log('[Overlay] scale factors:', scaleX, 'x', scaleY);
100
+ const isFrameLandscape = sourceFrameSize.width > sourceFrameSize.height;
101
+ const isScreenPortrait = screenHeight > screenWidth;
102
+ const needsRotation = isFrameLandscape && isScreenPortrait;
103
+
104
+ if (needsRotation) {
105
+ const scaleX = screenWidth / sourceFrameSize.height;
106
+ const scaleY = screenHeight / sourceFrameSize.width;
107
+
108
+ transformedQuad = sourceQuad.map((p) => ({
109
+ x: p.y * scaleX,
110
+ y: (sourceFrameSize.width - p.x) * scaleY,
111
+ }));
112
+ } else {
113
+ const scaleX = screenWidth / sourceFrameSize.width;
114
+ const scaleY = screenHeight / sourceFrameSize.height;
115
+
116
+ transformedQuad = sourceQuad.map((p) => ({
117
+ x: p.x * scaleX,
118
+ y: p.y * scaleY,
119
+ }));
83
120
  }
84
-
85
- transformedQuad = quad.map((p) => ({
86
- x: p.x * scaleX,
87
- y: p.y * scaleY,
88
- }));
89
121
  }
90
122
 
91
- if (__DEV__) {
92
- console.log('[Overlay] transformed quad:', transformedQuad);
123
+ if (!transformedQuad) {
124
+ return { outlinePath: null, gridPaths: [] };
93
125
  }
94
126
 
95
- const skPath = Skia.Path.Make();
96
- skPath.moveTo(transformedQuad[0].x, transformedQuad[0].y);
97
- transformedQuad.slice(1).forEach((p) => skPath.lineTo(p.x, p.y));
98
- skPath.close();
127
+ const skPath = buildPath(transformedQuad);
99
128
  const grid: ReturnType<typeof Skia.Path.Make>[] = [];
100
129
 
101
130
  if (showGrid) {
@@ -122,7 +151,7 @@ export const Overlay: React.FC<OverlayProps> = ({
122
151
  }
123
152
 
124
153
  return { outlinePath: skPath, gridPaths: grid };
125
- }, [quad, screenWidth, screenHeight, frameSize, showGrid]);
154
+ }, [quad, screenWidth, screenHeight, frameSize, showGrid, color]);
126
155
 
127
156
  if (__DEV__) {
128
157
  console.log('[Overlay] rendering Canvas with dimensions:', screenWidth, 'x', screenHeight);
@@ -134,7 +163,7 @@ export const Overlay: React.FC<OverlayProps> = ({
134
163
  {outlinePath && (
135
164
  <>
136
165
  <Path path={outlinePath} color={color} style="stroke" strokeWidth={8} />
137
- <Path path={outlinePath} color="rgba(231, 166, 73, 0.2)" style="fill" />
166
+ <Path path={outlinePath} color={fillColor} style="fill" />
138
167
  {gridPaths.map((gridPath, index) => (
139
168
  <Path
140
169
  // eslint-disable-next-line react/no-array-index-key