react-native-rectangle-doc-scanner 1.6.0 → 1.7.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.
@@ -36,6 +36,7 @@ interface Props {
36
36
  gridColor?: string;
37
37
  gridLineWidth?: number;
38
38
  detectionConfig?: DetectionConfig;
39
+ useNativeOverlay?: boolean;
39
40
  }
40
41
  export declare const DocScanner: React.ForwardRefExoticComponent<Props & React.RefAttributes<DocScannerHandle>>;
41
42
  export type { DocScannerHandle };
@@ -48,12 +48,18 @@ if (!NativeDocScannerModule) {
48
48
  const NativeDocScanner = (0, react_native_1.requireNativeComponent)(VIEW_NAME);
49
49
  const DEFAULT_OVERLAY_COLOR = '#e7a649';
50
50
  const GRID_COLOR_FALLBACK = 'rgba(231, 166, 73, 0.35)';
51
- exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAULT_OVERLAY_COLOR, autoCapture = true, minStableFrames = 8, enableTorch = false, quality = 90, useBase64 = false, children, showGrid = true, gridColor, gridLineWidth = 2, }, ref) => {
51
+ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAULT_OVERLAY_COLOR, autoCapture = true, minStableFrames = 8, enableTorch = false, quality = 90, useBase64 = false, children, showGrid = true, gridColor, gridLineWidth = 2, useNativeOverlay, }, ref) => {
52
52
  const viewRef = (0, react_1.useRef)(null);
53
53
  const capturingRef = (0, react_1.useRef)(false);
54
54
  const [quad, setQuad] = (0, react_1.useState)(null);
55
55
  const [stable, setStable] = (0, react_1.useState)(0);
56
56
  const [frameSize, setFrameSize] = (0, react_1.useState)(null);
57
+ const shouldUseNativeOverlay = (0, react_1.useMemo)(() => {
58
+ if (typeof useNativeOverlay === 'boolean') {
59
+ return useNativeOverlay;
60
+ }
61
+ return react_native_1.Platform.OS === 'ios';
62
+ }, [useNativeOverlay]);
57
63
  const effectiveGridColor = (0, react_1.useMemo)(() => gridColor ?? GRID_COLOR_FALLBACK, [gridColor]);
58
64
  const ensureViewHandle = (0, react_1.useCallback)(() => {
59
65
  const nodeHandle = (0, react_native_1.findNodeHandle)(viewRef.current);
@@ -156,7 +162,7 @@ exports.DocScanner = (0, react_1.forwardRef)(({ onCapture, overlayColor = DEFAUL
156
162
  }), [captureNative, resetNativeStability]);
157
163
  return (react_1.default.createElement(react_native_1.View, { style: styles.container },
158
164
  react_1.default.createElement(NativeDocScanner, { ref: viewRef, style: react_native_1.StyleSheet.absoluteFill, detectionCountBeforeCapture: minStableFrames, autoCapture: autoCapture, enableTorch: enableTorch, quality: quality, useBase64: useBase64, onRectangleDetect: handleRectangleDetect, onPictureTaken: handlePictureTaken }),
159
- react_1.default.createElement(overlay_1.Overlay, { quad: quad, color: overlayColor, frameSize: frameSize, showGrid: showGrid, gridColor: effectiveGridColor, gridLineWidth: gridLineWidth }),
165
+ !shouldUseNativeOverlay && (react_1.default.createElement(overlay_1.Overlay, { quad: quad, color: overlayColor, frameSize: frameSize, showGrid: showGrid, gridColor: effectiveGridColor, gridLineWidth: gridLineWidth })),
160
166
  !autoCapture && (react_1.default.createElement(react_native_1.TouchableOpacity, { style: styles.button, onPress: handleManualCapture })),
161
167
  children));
162
168
  });
@@ -28,6 +28,8 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
28
28
  private var previewLayer: AVCaptureVideoPreviewLayer?
29
29
  private let videoOutput = AVCaptureVideoDataOutput()
30
30
  private let photoOutput = AVCapturePhotoOutput()
31
+ private let outlineLayer = CAShapeLayer()
32
+ private let gridLayer = CAShapeLayer()
31
33
 
32
34
  private var currentStableCounter: Int = 0
33
35
  private var isProcessingFrame = false
@@ -51,6 +53,7 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
51
53
  private func commonInit() {
52
54
  backgroundColor = .black
53
55
  configurePreviewLayer()
56
+ configureOverlayLayers()
54
57
  configureSession()
55
58
  }
56
59
 
@@ -61,6 +64,23 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
61
64
  previewLayer = layer
62
65
  }
63
66
 
67
+ private func configureOverlayLayers() {
68
+ outlineLayer.strokeColor = UIColor(red: 0.18, green: 0.6, blue: 0.95, alpha: 1.0).cgColor
69
+ outlineLayer.fillColor = UIColor(red: 0.18, green: 0.6, blue: 0.95, alpha: 0.2).cgColor
70
+ outlineLayer.lineWidth = 4
71
+ outlineLayer.lineJoin = .round
72
+ outlineLayer.isHidden = true
73
+ layer.addSublayer(outlineLayer)
74
+
75
+ gridLayer.strokeColor = UIColor(red: 0.18, green: 0.6, blue: 0.95, alpha: 0.35).cgColor
76
+ gridLayer.fillColor = UIColor.clear.cgColor
77
+ gridLayer.lineWidth = 1.5
78
+ gridLayer.lineJoin = .round
79
+ gridLayer.isHidden = true
80
+ gridLayer.zPosition = outlineLayer.zPosition + 1
81
+ layer.addSublayer(gridLayer)
82
+ }
83
+
64
84
  private func configureSession() {
65
85
  sessionQueue.async { [weak self] in
66
86
  guard let self else { return }
@@ -115,6 +135,8 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
115
135
  if let connection = previewLayer?.connection, connection.isVideoOrientationSupported {
116
136
  connection.videoOrientation = .portrait
117
137
  }
138
+ outlineLayer.frame = bounds
139
+ gridLayer.frame = bounds
118
140
  }
119
141
 
120
142
  private func updateTorchMode() {
@@ -236,6 +258,8 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
236
258
  effectiveObservation = nil
237
259
  }
238
260
 
261
+ updateNativeOverlay(with: effectiveObservation)
262
+
239
263
  let payload: [String: Any?]
240
264
  if let observation = effectiveObservation {
241
265
  let points = [
@@ -276,6 +300,66 @@ class RNRDocScannerView: UIView, AVCaptureVideoDataOutputSampleBufferDelegate, A
276
300
  CGPoint(x: normalizedPoint.x * frameSize.width, y: (1 - normalizedPoint.y) * frameSize.height)
277
301
  }
278
302
 
303
+ private func updateNativeOverlay(with observation: VNRectangleObservation?) {
304
+ DispatchQueue.main.async {
305
+ guard let observation else {
306
+ self.outlineLayer.path = nil
307
+ self.gridLayer.path = nil
308
+ self.outlineLayer.isHidden = true
309
+ self.gridLayer.isHidden = true
310
+ return
311
+ }
312
+
313
+ guard let previewLayer = self.previewLayer else {
314
+ return
315
+ }
316
+
317
+ let points = [
318
+ self.convertToLayerPoint(observation.topLeft, previewLayer: previewLayer),
319
+ self.convertToLayerPoint(observation.topRight, previewLayer: previewLayer),
320
+ self.convertToLayerPoint(observation.bottomRight, previewLayer: previewLayer),
321
+ self.convertToLayerPoint(observation.bottomLeft, previewLayer: previewLayer),
322
+ ]
323
+
324
+ let outline = UIBezierPath()
325
+ outline.move(to: points[0])
326
+ outline.addLine(to: points[1])
327
+ outline.addLine(to: points[2])
328
+ outline.addLine(to: points[3])
329
+ outline.close()
330
+
331
+ self.outlineLayer.path = outline.cgPath
332
+ self.outlineLayer.isHidden = false
333
+
334
+ let gridPath = UIBezierPath()
335
+ let steps: [CGFloat] = [1.0 / 3.0, 2.0 / 3.0]
336
+
337
+ for step in steps {
338
+ let startVertical = self.interpolate(points[0], points[1], t: step)
339
+ let endVertical = self.interpolate(points[3], points[2], t: step)
340
+ gridPath.move(to: startVertical)
341
+ gridPath.addLine(to: endVertical)
342
+
343
+ let startHorizontal = self.interpolate(points[0], points[3], t: step)
344
+ let endHorizontal = self.interpolate(points[1], points[2], t: step)
345
+ gridPath.move(to: startHorizontal)
346
+ gridPath.addLine(to: endHorizontal)
347
+ }
348
+
349
+ self.gridLayer.path = gridPath.cgPath
350
+ self.gridLayer.isHidden = false
351
+ }
352
+ }
353
+
354
+ private func convertToLayerPoint(_ normalizedPoint: CGPoint, previewLayer: AVCaptureVideoPreviewLayer) -> CGPoint {
355
+ let devicePoint = CGPoint(x: normalizedPoint.x, y: 1 - normalizedPoint.y)
356
+ return previewLayer.layerPointConverted(fromCaptureDevicePoint: devicePoint)
357
+ }
358
+
359
+ private func interpolate(_ start: CGPoint, _ end: CGPoint, t: CGFloat) -> CGPoint {
360
+ CGPoint(x: start.x + (end.x - start.x) * t, y: start.y + (end.y - start.y) * t)
361
+ }
362
+
279
363
  // MARK: - Capture
280
364
 
281
365
  func capture(completion: @escaping (Result<RNRDocScannerCaptureResult, Error>) -> Void) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-rectangle-doc-scanner",
3
- "version": "1.6.0",
3
+ "version": "1.7.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 React, {
8
8
  useState,
9
9
  } from 'react';
10
10
  import {
11
+ Platform,
11
12
  findNodeHandle,
12
13
  NativeModules,
13
14
  requireNativeComponent,
@@ -93,6 +94,7 @@ interface Props {
93
94
  gridColor?: string;
94
95
  gridLineWidth?: number;
95
96
  detectionConfig?: DetectionConfig;
97
+ useNativeOverlay?: boolean;
96
98
  }
97
99
 
98
100
  const DEFAULT_OVERLAY_COLOR = '#e7a649';
@@ -110,6 +112,7 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(({
110
112
  showGrid = true,
111
113
  gridColor,
112
114
  gridLineWidth = 2,
115
+ useNativeOverlay,
113
116
  }, ref) => {
114
117
  const viewRef = useRef<NativeDocScannerInstance | null>(null);
115
118
  const capturingRef = useRef(false);
@@ -117,6 +120,13 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(({
117
120
  const [stable, setStable] = useState(0);
118
121
  const [frameSize, setFrameSize] = useState<{ width: number; height: number } | null>(null);
119
122
 
123
+ const shouldUseNativeOverlay = useMemo(() => {
124
+ if (typeof useNativeOverlay === 'boolean') {
125
+ return useNativeOverlay;
126
+ }
127
+ return Platform.OS === 'ios';
128
+ }, [useNativeOverlay]);
129
+
120
130
  const effectiveGridColor = useMemo(
121
131
  () => gridColor ?? GRID_COLOR_FALLBACK,
122
132
  [gridColor],
@@ -261,14 +271,16 @@ export const DocScanner = forwardRef<DocScannerHandle, Props>(({
261
271
  onRectangleDetect={handleRectangleDetect}
262
272
  onPictureTaken={handlePictureTaken}
263
273
  />
264
- <Overlay
265
- quad={quad}
266
- color={overlayColor}
267
- frameSize={frameSize}
268
- showGrid={showGrid}
269
- gridColor={effectiveGridColor}
270
- gridLineWidth={gridLineWidth}
271
- />
274
+ {!shouldUseNativeOverlay && (
275
+ <Overlay
276
+ quad={quad}
277
+ color={overlayColor}
278
+ frameSize={frameSize}
279
+ showGrid={showGrid}
280
+ gridColor={effectiveGridColor}
281
+ gridLineWidth={gridLineWidth}
282
+ />
283
+ )}
272
284
  {!autoCapture && (
273
285
  <TouchableOpacity style={styles.button} onPress={handleManualCapture} />
274
286
  )}