react-native-rectangle-doc-scanner 15.3.0 → 15.5.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/README.md +1135 -9
- package/dist/FullDocScanner.js +13 -7
- package/package.json +1 -1
- package/src/FullDocScanner.tsx +13 -7
package/README.md
CHANGED
|
@@ -1,8 +1,639 @@
|
|
|
1
1
|
# React Native Document Scanner Wrapper
|
|
2
2
|
|
|
3
|
+
[English](#english-version) | [한국어](#한국어-버전)
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 한국어 버전
|
|
8
|
+
|
|
9
|
+
React Native용 문서 스캐너 라이브러리입니다. [`react-native-document-scanner`](https://github.com/Michaelvilleneuve/react-native-document-scanner)를 래핑하여 iOS와 Android 모두에서 네이티브 문서 스캐너를 제공합니다.
|
|
10
|
+
|
|
11
|
+
> 네이티브 구현은 업스트림 라이브러리에 포함되어 있습니다 (iOS: Objective-C/OpenCV, Android: Kotlin/OpenCV). 이 패키지는 타입 안전한 래퍼, 선택적 크롭 에디터 헬퍼, 전체 화면 스캐너를 제공합니다.
|
|
12
|
+
|
|
13
|
+
## ✨ 전문가급 카메라 품질 (v3.2+)
|
|
14
|
+
|
|
15
|
+
**주요 업데이트:** 최신 `AVCapturePhotoOutput` API로 업그레이드되어 이미지 품질이 대폭 향상되었습니다!
|
|
16
|
+
|
|
17
|
+
### 🚀 새로운 기능:
|
|
18
|
+
- **최신 카메라 API** - 구형 `AVCaptureStillImageOutput` 대신 `AVCapturePhotoOutput` (iOS 10+) 사용
|
|
19
|
+
- **iPhone 네이티브 품질** - 기본 카메라 앱과 동일한 품질
|
|
20
|
+
- **컴퓨테이셔널 포토그래피** - 자동 HDR, Deep Fusion, Smart HDR 지원
|
|
21
|
+
- **12MP+ 해상도** - 최신 iPhone에서 전체 해상도 캡처 (iPhone 14 Pro+ 기준 최대 48MP)
|
|
22
|
+
- **최대 품질 우선순위** - iOS 13+ 품질 우선순위 활성화
|
|
23
|
+
- **95%+ JPEG 품질** - 품질 손실 방지를 위한 최소 압축 품질 강제
|
|
24
|
+
|
|
25
|
+
### 🎯 자동 최적화:
|
|
26
|
+
- **고해상도 캡처** - 전체 센서 해상도 활성화 (`AVCaptureSessionPresetHigh`)
|
|
27
|
+
- **최소 95% JPEG** - 압축으로 인한 품질 저하 방지
|
|
28
|
+
- **고급 기능**:
|
|
29
|
+
- 더 선명한 이미지를 위한 비디오 안정화
|
|
30
|
+
- 항상 선명한 캡처를 위한 연속 자동 초점
|
|
31
|
+
- 자동 노출 및 화이트 밸런스
|
|
32
|
+
- 어두운 환경에서 저조도 부스트
|
|
33
|
+
- **하드웨어 가속** - 효율적인 처리를 위한 CIContext
|
|
34
|
+
|
|
35
|
+
### ⚡ 완전 자동 설치:
|
|
36
|
+
yarn/npm으로 설치하기만 하면 됩니다 - **수동 설정 불필요!**
|
|
37
|
+
- Postinstall 스크립트가 자동으로 카메라 품질 패치
|
|
38
|
+
- 설치 중 iOS 최적화 파일 자동 복사
|
|
39
|
+
- `pod install` 후 즉시 사용 가능
|
|
40
|
+
|
|
41
|
+
## 빠른 시작 가이드
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# 1. 패키지 설치
|
|
45
|
+
yarn add react-native-rectangle-doc-scanner \
|
|
46
|
+
github:Michaelvilleneuve/react-native-document-scanner \
|
|
47
|
+
react-native-perspective-image-cropper \
|
|
48
|
+
react-native-fs \
|
|
49
|
+
react-native-image-crop-picker \
|
|
50
|
+
react-native-image-picker \
|
|
51
|
+
react-native-svg \
|
|
52
|
+
expo-modules-core
|
|
53
|
+
|
|
54
|
+
# 2. iOS 설정
|
|
55
|
+
cd ios && pod install && cd ..
|
|
56
|
+
|
|
57
|
+
# 3. iOS Info.plist에 카메라 권한 추가 (수동)
|
|
58
|
+
# 4. 앱 실행
|
|
59
|
+
npx react-native run-ios
|
|
60
|
+
# 또는
|
|
61
|
+
npx react-native run-android
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 설치 방법
|
|
65
|
+
|
|
66
|
+
### 1. 패키지 설치
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
yarn add react-native-rectangle-doc-scanner \
|
|
70
|
+
github:Michaelvilleneuve/react-native-document-scanner \
|
|
71
|
+
react-native-perspective-image-cropper
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
또는 npm 사용:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npm install react-native-rectangle-doc-scanner \
|
|
78
|
+
github:Michaelvilleneuve/react-native-document-scanner \
|
|
79
|
+
react-native-perspective-image-cropper
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 2. Peer Dependencies 설치
|
|
83
|
+
|
|
84
|
+
이 라이브러리는 다음 peer dependencies를 필요로 합니다:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
yarn add react-native-fs \
|
|
88
|
+
react-native-image-crop-picker \
|
|
89
|
+
react-native-image-picker \
|
|
90
|
+
react-native-svg \
|
|
91
|
+
expo-modules-core
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
또는 npm 사용:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install react-native-fs \
|
|
98
|
+
react-native-image-crop-picker \
|
|
99
|
+
react-native-image-picker \
|
|
100
|
+
react-native-svg \
|
|
101
|
+
expo-modules-core
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**선택사항 (이미지 회전 기능을 사용하려면):**
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 둘 중 하나 선택
|
|
108
|
+
yarn add expo-image-manipulator
|
|
109
|
+
# 또는
|
|
110
|
+
yarn add react-native-image-rotate
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 2-1. Babel 및 Reanimated 설정 (필요시)
|
|
114
|
+
|
|
115
|
+
프로젝트에 `babel.config.js` 파일이 있는 경우, 다음 플러그인이 필요할 수 있습니다:
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
module.exports = {
|
|
119
|
+
presets: ['module:@react-native/babel-preset'],
|
|
120
|
+
plugins: [
|
|
121
|
+
'react-native-reanimated/plugin' // 마지막에 위치해야 함
|
|
122
|
+
],
|
|
123
|
+
};
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**필요한 경우 추가 패키지:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
yarn add react-native-reanimated
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 3. iOS 설정
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
cd ios && pod install && cd ..
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Info.plist에 카메라 권한 추가:**
|
|
139
|
+
|
|
140
|
+
`ios/YourApp/Info.plist` 파일에 다음 권한을 추가하세요:
|
|
141
|
+
|
|
142
|
+
```xml
|
|
143
|
+
<key>NSCameraUsageDescription</key>
|
|
144
|
+
<string>문서를 스캔하기 위해 카메라 접근이 필요합니다</string>
|
|
145
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
146
|
+
<string>스캔한 문서를 저장하기 위해 사진 라이브러리 접근이 필요합니다</string>
|
|
147
|
+
<key>NSPhotoLibraryAddUsageDescription</key>
|
|
148
|
+
<string>스캔한 문서를 저장하기 위해 사진 라이브러리 접근이 필요합니다</string>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 4. Android 설정
|
|
152
|
+
|
|
153
|
+
Android는 자동으로 네이티브 모듈을 링크합니다. 레거시 아키텍처를 사용하는 경우, `MainApplication.java`에서 `DocumentScannerPackage()`를 수동으로 등록해야 합니다.
|
|
154
|
+
|
|
155
|
+
**AndroidManifest.xml에 권한 추가:**
|
|
156
|
+
|
|
157
|
+
`android/app/src/main/AndroidManifest.xml` 파일에 다음 권한이 자동으로 포함됩니다:
|
|
158
|
+
|
|
159
|
+
```xml
|
|
160
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
161
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
|
162
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
|
163
|
+
|
|
164
|
+
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
|
165
|
+
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
|
166
|
+
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Gradle 설정:**
|
|
170
|
+
|
|
171
|
+
라이브러리는 다음 최소 요구사항을 가지고 있습니다:
|
|
172
|
+
- `minSdkVersion`: 21
|
|
173
|
+
- `compileSdkVersion`: 33
|
|
174
|
+
- `targetSdkVersion`: 33
|
|
175
|
+
- Kotlin: 1.8.21
|
|
176
|
+
- Java: 17
|
|
177
|
+
|
|
178
|
+
이 설정은 자동으로 적용되지만, 프로젝트의 `android/build.gradle`에서 호환되는 버전을 사용하는지 확인하세요.
|
|
179
|
+
|
|
180
|
+
**프로젝트의 `android/build.gradle` 예시:**
|
|
181
|
+
|
|
182
|
+
```gradle
|
|
183
|
+
buildscript {
|
|
184
|
+
ext {
|
|
185
|
+
buildToolsVersion = "33.0.0"
|
|
186
|
+
minSdkVersion = 21
|
|
187
|
+
compileSdkVersion = 33
|
|
188
|
+
targetSdkVersion = 33
|
|
189
|
+
kotlinVersion = "1.8.21"
|
|
190
|
+
}
|
|
191
|
+
dependencies {
|
|
192
|
+
classpath("com.android.tools.build:gradle:7.4.2")
|
|
193
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**프로젝트의 `android/app/build.gradle` 예시:**
|
|
199
|
+
|
|
200
|
+
```gradle
|
|
201
|
+
android {
|
|
202
|
+
compileSdkVersion rootProject.ext.compileSdkVersion
|
|
203
|
+
|
|
204
|
+
compileOptions {
|
|
205
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
206
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
kotlinOptions {
|
|
210
|
+
jvmTarget = '17'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
defaultConfig {
|
|
214
|
+
minSdkVersion rootProject.ext.minSdkVersion
|
|
215
|
+
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 5. 자동 품질 패치 (Postinstall)
|
|
221
|
+
|
|
222
|
+
이 라이브러리는 **postinstall 스크립트**를 통해 자동으로 카메라 품질을 최적화합니다:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# 패키지 설치 시 자동 실행됨
|
|
226
|
+
node scripts/postinstall.js
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**postinstall이 하는 일:**
|
|
230
|
+
1. `react-native-document-scanner` 패키지를 찾습니다 (node_modules에서 자동 감지)
|
|
231
|
+
2. vendor 폴더의 최적화된 iOS 파일들을 복사합니다:
|
|
232
|
+
- `IPDFCameraViewController.m/h` - AVCapturePhotoOutput 사용
|
|
233
|
+
- `DocumentScannerView.m/h` - 고품질 설정
|
|
234
|
+
- `RNPdfScannerManager.m/h` - 네이티브 브릿지
|
|
235
|
+
- `ios.js`, `index.js` - JavaScript 인터페이스
|
|
236
|
+
3. 원본 파일은 `.original` 확장자로 백업됩니다
|
|
237
|
+
|
|
238
|
+
**수동으로 실행하려면:**
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
npm run postinstall
|
|
242
|
+
# 또는
|
|
243
|
+
node scripts/postinstall.js
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**문제 해결:**
|
|
247
|
+
- postinstall이 실패하는 경우, `react-native-document-scanner`가 설치되어 있는지 확인하세요
|
|
248
|
+
- yarn workspaces나 monorepo를 사용하는 경우, 패키지 호이스팅으로 인해 경로가 다를 수 있습니다
|
|
249
|
+
|
|
250
|
+
### 6. 런타임 권한 요청
|
|
251
|
+
|
|
252
|
+
앱에서 런타임에 카메라 권한을 요청해야 합니다:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { PermissionsAndroid, Platform } from 'react-native';
|
|
256
|
+
|
|
257
|
+
async function requestCameraPermission() {
|
|
258
|
+
if (Platform.OS === 'android') {
|
|
259
|
+
try {
|
|
260
|
+
const granted = await PermissionsAndroid.request(
|
|
261
|
+
PermissionsAndroid.PERMISSIONS.CAMERA,
|
|
262
|
+
{
|
|
263
|
+
title: '카메라 권한',
|
|
264
|
+
message: '문서를 스캔하기 위해 카메라 접근이 필요합니다',
|
|
265
|
+
buttonNeutral: '나중에',
|
|
266
|
+
buttonNegative: '거부',
|
|
267
|
+
buttonPositive: '허용',
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
return granted === PermissionsAndroid.RESULTS.GRANTED;
|
|
271
|
+
} catch (err) {
|
|
272
|
+
console.warn(err);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 사용 방법
|
|
281
|
+
|
|
282
|
+
### 기본 사용 예제
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
import React, { useRef } from 'react';
|
|
286
|
+
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
287
|
+
import { DocScanner, type DocScannerHandle } from 'react-native-rectangle-doc-scanner';
|
|
288
|
+
|
|
289
|
+
export const ScanScreen = () => {
|
|
290
|
+
const scannerRef = useRef<DocScannerHandle>(null);
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<View style={styles.container}>
|
|
294
|
+
<DocScanner
|
|
295
|
+
ref={scannerRef}
|
|
296
|
+
overlayColor="rgba(0, 126, 244, 0.35)"
|
|
297
|
+
autoCapture
|
|
298
|
+
minStableFrames={6}
|
|
299
|
+
onCapture={(result) => {
|
|
300
|
+
console.log('문서 캡처됨:', result.path);
|
|
301
|
+
console.log('크기:', result.width, 'x', result.height);
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
<View style={styles.overlay} pointerEvents="none">
|
|
305
|
+
<Text style={styles.hint}>프레임 안에 문서를 정렬하세요</Text>
|
|
306
|
+
</View>
|
|
307
|
+
</DocScanner>
|
|
308
|
+
|
|
309
|
+
<TouchableOpacity
|
|
310
|
+
style={styles.captureButton}
|
|
311
|
+
onPress={() => scannerRef.current?.capture()}
|
|
312
|
+
>
|
|
313
|
+
<Text style={styles.captureButtonText}>촬영</Text>
|
|
314
|
+
</TouchableOpacity>
|
|
315
|
+
</View>
|
|
316
|
+
);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const styles = StyleSheet.create({
|
|
320
|
+
container: {
|
|
321
|
+
flex: 1,
|
|
322
|
+
backgroundColor: '#000'
|
|
323
|
+
},
|
|
324
|
+
overlay: {
|
|
325
|
+
position: 'absolute',
|
|
326
|
+
top: 60,
|
|
327
|
+
alignSelf: 'center',
|
|
328
|
+
paddingHorizontal: 20,
|
|
329
|
+
paddingVertical: 10,
|
|
330
|
+
borderRadius: 12,
|
|
331
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
332
|
+
},
|
|
333
|
+
hint: {
|
|
334
|
+
color: '#fff',
|
|
335
|
+
fontWeight: '600'
|
|
336
|
+
},
|
|
337
|
+
captureButton: {
|
|
338
|
+
position: 'absolute',
|
|
339
|
+
bottom: 40,
|
|
340
|
+
alignSelf: 'center',
|
|
341
|
+
width: 70,
|
|
342
|
+
height: 70,
|
|
343
|
+
borderRadius: 35,
|
|
344
|
+
backgroundColor: '#fff',
|
|
345
|
+
justifyContent: 'center',
|
|
346
|
+
alignItems: 'center',
|
|
347
|
+
},
|
|
348
|
+
captureButtonText: {
|
|
349
|
+
color: '#000',
|
|
350
|
+
fontWeight: '600',
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Props
|
|
356
|
+
|
|
357
|
+
`<DocScanner />` 컴포넌트는 다음 props를 지원합니다:
|
|
358
|
+
|
|
359
|
+
| Prop | 타입 | 기본값 | 설명 |
|
|
360
|
+
| --- | --- | --- | --- |
|
|
361
|
+
| `overlayColor` | `string` | `#0b7ef4` | 네이티브 오버레이 색상 |
|
|
362
|
+
| `autoCapture` | `boolean` | `true` | 자동 캡처 활성화 (내부적으로 `manualOnly`로 매핑됨) |
|
|
363
|
+
| `minStableFrames` | `number` | `8` | 자동 캡처 전 필요한 안정적인 프레임 수 |
|
|
364
|
+
| `enableTorch` | `boolean` | `false` | 플래시 켜기/끄기 |
|
|
365
|
+
| `quality` | `number` | `90` | 이미지 품질 (0–100, 네이티브용으로 변환됨) |
|
|
366
|
+
| `useBase64` | `boolean` | `false` | 파일 URI 대신 base64로 반환 |
|
|
367
|
+
| `onCapture` | `(result) => void` | — | `{ path, quad: null, width, height }` 객체를 전달받음 |
|
|
368
|
+
|
|
369
|
+
### 수동 캡처
|
|
370
|
+
|
|
371
|
+
ref를 통해 `capture()` 메서드를 사용하여 수동으로 캡처할 수 있습니다. children을 사용하여 카메라 프리뷰 위에 커스텀 UI(버튼, 진행 표시기, 온보딩 팁 등)를 렌더링할 수 있습니다.
|
|
372
|
+
|
|
373
|
+
## 추가 API
|
|
374
|
+
|
|
375
|
+
### CropEditor
|
|
376
|
+
|
|
377
|
+
`react-native-perspective-image-cropper`를 래핑하여 수동으로 모서리를 조정할 수 있는 크롭 에디터를 제공합니다.
|
|
378
|
+
|
|
379
|
+
```tsx
|
|
380
|
+
import { CropEditor } from 'react-native-rectangle-doc-scanner';
|
|
381
|
+
|
|
382
|
+
<CropEditor
|
|
383
|
+
imagePath={capturedImagePath}
|
|
384
|
+
onCropComplete={(croppedPath) => {
|
|
385
|
+
console.log('크롭된 이미지:', croppedPath);
|
|
386
|
+
}}
|
|
387
|
+
onCancel={() => {
|
|
388
|
+
console.log('크롭 취소');
|
|
389
|
+
}}
|
|
390
|
+
/>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### FullDocScanner
|
|
394
|
+
|
|
395
|
+
스캐너와 크롭 에디터를 단일 모달형 플로우로 제공합니다. `expo-image-manipulator` 또는 `react-native-image-rotate`가 설치되어 있으면, 확인 화면에서 90° 회전 버튼이 표시됩니다.
|
|
396
|
+
|
|
397
|
+
```tsx
|
|
398
|
+
import { FullDocScanner } from 'react-native-rectangle-doc-scanner';
|
|
399
|
+
|
|
400
|
+
<FullDocScanner
|
|
401
|
+
onComplete={(result) => {
|
|
402
|
+
console.log('완료:', result);
|
|
403
|
+
}}
|
|
404
|
+
onCancel={() => {
|
|
405
|
+
console.log('취소');
|
|
406
|
+
}}
|
|
407
|
+
/>
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## 의존성 패키지 상세 정보
|
|
411
|
+
|
|
412
|
+
이 라이브러리는 다양한 패키지에 의존합니다. 각 패키지의 역할은 다음과 같습니다:
|
|
413
|
+
|
|
414
|
+
### 필수 의존성 (Peer Dependencies)
|
|
415
|
+
|
|
416
|
+
| 패키지 | 역할 | 필수 여부 |
|
|
417
|
+
|--------|------|-----------|
|
|
418
|
+
| `react-native-fs` | 파일 시스템 접근 (이미지 저장/읽기) | ✅ 필수 |
|
|
419
|
+
| `react-native-image-crop-picker` | 이미지 선택 및 크롭 | ✅ 필수 |
|
|
420
|
+
| `react-native-image-picker` | 갤러리/카메라에서 이미지 선택 | ✅ 필수 |
|
|
421
|
+
| `react-native-svg` | SVG 렌더링 (UI 오버레이) | ✅ 필수 |
|
|
422
|
+
| `expo-modules-core` | Expo 모듈 코어 기능 | ✅ 필수 |
|
|
423
|
+
| `expo-image-manipulator` | 이미지 회전 및 편집 | ⚙️ 선택 (회전 기능용) |
|
|
424
|
+
| `react-native-image-rotate` | 이미지 회전 (대안) | ⚙️ 선택 (회전 기능용) |
|
|
425
|
+
|
|
426
|
+
### 내부 의존성 (Dependencies)
|
|
427
|
+
|
|
428
|
+
| 패키지 | 역할 |
|
|
429
|
+
|--------|------|
|
|
430
|
+
| `react-native-document-scanner` | 네이티브 문서 스캐너 구현 (GitHub) |
|
|
431
|
+
| `react-native-perspective-image-cropper` | 원근 보정 크롭 에디터 |
|
|
432
|
+
| `prop-types` | React PropTypes 검증 |
|
|
433
|
+
|
|
434
|
+
### 개발 의존성 (DevDependencies)
|
|
435
|
+
|
|
436
|
+
| 패키지 | 역할 |
|
|
437
|
+
|--------|------|
|
|
438
|
+
| `typescript` | TypeScript 컴파일러 |
|
|
439
|
+
| `@types/react` | React 타입 정의 |
|
|
440
|
+
| `@types/react-native` | React Native 타입 정의 |
|
|
441
|
+
| `@types/react-native-fs` | react-native-fs 타입 정의 |
|
|
442
|
+
|
|
443
|
+
### 네이티브 의존성
|
|
444
|
+
|
|
445
|
+
**iOS (CocoaPods):**
|
|
446
|
+
- OpenCV (이미지 처리 및 문서 감지)
|
|
447
|
+
- AVFoundation (카메라 API)
|
|
448
|
+
- CoreImage (이미지 필터 및 품질 처리)
|
|
449
|
+
|
|
450
|
+
**Android (Gradle):**
|
|
451
|
+
- OpenCV 4.9.0 (문서 감지)
|
|
452
|
+
- CameraX 1.3.0 (카메라 API)
|
|
453
|
+
- Kotlin Coroutines 1.7.3 (비동기 처리)
|
|
454
|
+
- ML Kit Document Scanner (문서 스캔)
|
|
455
|
+
- ML Kit Object Detection (실시간 사각형 감지)
|
|
456
|
+
- AndroidX Core, AppCompat (Android 기본 라이브러리)
|
|
457
|
+
|
|
458
|
+
## 기술 스택
|
|
459
|
+
|
|
460
|
+
### iOS
|
|
461
|
+
- **언어**: Objective-C
|
|
462
|
+
- **카메라 API**: AVCapturePhotoOutput (iOS 10+)
|
|
463
|
+
- **이미지 처리**: OpenCV, CoreImage (CIContext)
|
|
464
|
+
- **최소 버전**: iOS 11.0
|
|
465
|
+
- **지원 아키텍처**: arm64, x86_64 (시뮬레이터)
|
|
466
|
+
|
|
467
|
+
### Android
|
|
468
|
+
- **언어**: Kotlin 1.8.21
|
|
469
|
+
- **카메라**: CameraX 1.3.0, Camera2 API
|
|
470
|
+
- **이미지 처리**: OpenCV 4.9.0
|
|
471
|
+
- **ML Kit**: 문서 스캔 및 객체 감지
|
|
472
|
+
- **최소 SDK**: 21 (Android 5.0 Lollipop)
|
|
473
|
+
- **타겟 SDK**: 33 (Android 13 Tiramisu)
|
|
474
|
+
- **Java**: JDK 17
|
|
475
|
+
- **Gradle**: 7.4.2+
|
|
476
|
+
- **Android Gradle Plugin**: 7.4.2+
|
|
477
|
+
|
|
478
|
+
## 문제 해결
|
|
479
|
+
|
|
480
|
+
### iOS 빌드 오류
|
|
481
|
+
|
|
482
|
+
**Pod 설치 후에도 빌드 오류가 발생하는 경우:**
|
|
483
|
+
|
|
484
|
+
```bash
|
|
485
|
+
cd ios
|
|
486
|
+
rm -rf Pods Podfile.lock
|
|
487
|
+
pod cache clean --all
|
|
488
|
+
pod install
|
|
489
|
+
cd ..
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**"Module not found" 또는 헤더 파일 관련 오류:**
|
|
493
|
+
|
|
494
|
+
```bash
|
|
495
|
+
# Xcode에서 Product > Clean Build Folder (Shift + Cmd + K)
|
|
496
|
+
# 또는 터미널에서:
|
|
497
|
+
cd ios
|
|
498
|
+
xcodebuild clean -workspace YourApp.xcworkspace -scheme YourApp
|
|
499
|
+
cd ..
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**CocoaPods 버전 문제:**
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
sudo gem install cocoapods
|
|
506
|
+
pod --version # 1.11.0 이상 권장
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Android 빌드 오류
|
|
510
|
+
|
|
511
|
+
**Gradle 빌드 오류가 발생하는 경우:**
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
cd android
|
|
515
|
+
./gradlew clean
|
|
516
|
+
./gradlew --stop # Gradle daemon 중지
|
|
517
|
+
cd ..
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
**Java 버전 오류:**
|
|
521
|
+
|
|
522
|
+
이 라이브러리는 Java 17이 필요합니다. Java 버전을 확인하세요:
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
java -version # java version "17.x.x" 확인
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
**Kotlin 버전 충돌:**
|
|
529
|
+
|
|
530
|
+
`android/build.gradle`에서 Kotlin 버전이 1.8.21 이상인지 확인:
|
|
531
|
+
|
|
532
|
+
```gradle
|
|
533
|
+
buildscript {
|
|
534
|
+
ext.kotlin_version = '1.8.21'
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
**OpenCV 의존성 오류:**
|
|
539
|
+
|
|
540
|
+
OpenCV가 자동으로 다운로드되지 않는 경우:
|
|
541
|
+
|
|
542
|
+
```bash
|
|
543
|
+
cd android
|
|
544
|
+
./gradlew clean
|
|
545
|
+
./gradlew :app:dependencies # 의존성 확인
|
|
546
|
+
cd ..
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### 권한 오류
|
|
550
|
+
|
|
551
|
+
**카메라가 작동하지 않는 경우:**
|
|
552
|
+
|
|
553
|
+
1. **iOS**: Info.plist에 권한 설명이 추가되어 있는지 확인:
|
|
554
|
+
- `NSCameraUsageDescription`
|
|
555
|
+
- `NSPhotoLibraryUsageDescription`
|
|
556
|
+
- `NSPhotoLibraryAddUsageDescription`
|
|
557
|
+
|
|
558
|
+
2. **Android**: PermissionsAndroid로 런타임 권한 요청:
|
|
559
|
+
```typescript
|
|
560
|
+
await PermissionsAndroid.request(
|
|
561
|
+
PermissionsAndroid.PERMISSIONS.CAMERA
|
|
562
|
+
);
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
3. 기기 설정에서 앱의 카메라 권한이 허용되어 있는지 확인
|
|
566
|
+
|
|
567
|
+
### Postinstall 스크립트 오류
|
|
568
|
+
|
|
569
|
+
**postinstall이 실행되지 않는 경우:**
|
|
570
|
+
|
|
571
|
+
```bash
|
|
572
|
+
# 수동으로 postinstall 실행
|
|
573
|
+
node node_modules/react-native-rectangle-doc-scanner/scripts/postinstall.js
|
|
574
|
+
|
|
575
|
+
# 또는 패키지 재설치
|
|
576
|
+
rm -rf node_modules
|
|
577
|
+
yarn install # 또는 npm install
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
**"react-native-document-scanner not found" 오류:**
|
|
581
|
+
|
|
582
|
+
```bash
|
|
583
|
+
# react-native-document-scanner 설치 확인
|
|
584
|
+
yarn add github:Michaelvilleneuve/react-native-document-scanner
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### Metro Bundler 오류
|
|
588
|
+
|
|
589
|
+
**"Unable to resolve module" 오류:**
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
# Metro 캐시 삭제
|
|
593
|
+
npx react-native start --reset-cache
|
|
594
|
+
|
|
595
|
+
# 또는
|
|
596
|
+
rm -rf $TMPDIR/metro-*
|
|
597
|
+
rm -rf $TMPDIR/haste-*
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Peer Dependencies 경고
|
|
601
|
+
|
|
602
|
+
**"unmet peer dependency" 경고가 나타나는 경우:**
|
|
603
|
+
|
|
604
|
+
모든 peer dependencies를 설치했는지 확인:
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
yarn add react-native-fs \
|
|
608
|
+
react-native-image-crop-picker \
|
|
609
|
+
react-native-image-picker \
|
|
610
|
+
react-native-svg \
|
|
611
|
+
expo-modules-core
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Expo 프로젝트
|
|
615
|
+
|
|
616
|
+
Expo를 사용하는 경우, 일부 네이티브 모듈이 Expo Go에서 작동하지 않을 수 있습니다.
|
|
617
|
+
개발 빌드(development build)를 사용하세요:
|
|
618
|
+
|
|
619
|
+
```bash
|
|
620
|
+
npx expo prebuild
|
|
621
|
+
npx expo run:ios
|
|
622
|
+
# 또는
|
|
623
|
+
npx expo run:android
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## 라이선스
|
|
627
|
+
|
|
628
|
+
MIT
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## English Version
|
|
633
|
+
|
|
3
634
|
React Native-friendly wrapper around [`react-native-document-scanner`](https://github.com/Michaelvilleneuve/react-native-document-scanner). It exposes a declarative `<DocScanner />` component that renders the native document scanner on both iOS and Android while keeping the surface area small enough to plug into custom UIs.
|
|
4
635
|
|
|
5
|
-
> The native implementation lives inside the upstream library (Objective
|
|
636
|
+
> The native implementation lives inside the upstream library (Objective-C/OpenCV on iOS, Kotlin/OpenCV on Android). This package simply re-exports a type-safe wrapper, optional crop editor helpers, and a full-screen scanner flow.
|
|
6
637
|
|
|
7
638
|
## ✨ Professional Camera Quality (v3.2+)
|
|
8
639
|
|
|
@@ -32,21 +663,249 @@ Just install with yarn/npm - **no manual configuration needed!**
|
|
|
32
663
|
- Optimized iOS files copied during installation
|
|
33
664
|
- Works immediately after `pod install`
|
|
34
665
|
|
|
666
|
+
## Quick Start Guide
|
|
667
|
+
|
|
668
|
+
```bash
|
|
669
|
+
# 1. Install packages
|
|
670
|
+
yarn add react-native-rectangle-doc-scanner \
|
|
671
|
+
github:Michaelvilleneuve/react-native-document-scanner \
|
|
672
|
+
react-native-perspective-image-cropper \
|
|
673
|
+
react-native-fs \
|
|
674
|
+
react-native-image-crop-picker \
|
|
675
|
+
react-native-image-picker \
|
|
676
|
+
react-native-svg \
|
|
677
|
+
expo-modules-core
|
|
678
|
+
|
|
679
|
+
# 2. iOS setup
|
|
680
|
+
cd ios && pod install && cd ..
|
|
681
|
+
|
|
682
|
+
# 3. Add camera permissions to iOS Info.plist (manual)
|
|
683
|
+
# 4. Run your app
|
|
684
|
+
npx react-native run-ios
|
|
685
|
+
# or
|
|
686
|
+
npx react-native run-android
|
|
687
|
+
```
|
|
688
|
+
|
|
35
689
|
## Installation
|
|
36
690
|
|
|
691
|
+
### 1. Install the Package
|
|
692
|
+
|
|
37
693
|
```bash
|
|
38
694
|
yarn add react-native-rectangle-doc-scanner \
|
|
39
695
|
github:Michaelvilleneuve/react-native-document-scanner \
|
|
40
696
|
react-native-perspective-image-cropper
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
Or using npm:
|
|
700
|
+
|
|
701
|
+
```bash
|
|
702
|
+
npm install react-native-rectangle-doc-scanner \
|
|
703
|
+
github:Michaelvilleneuve/react-native-document-scanner \
|
|
704
|
+
react-native-perspective-image-cropper
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
### 2. Install Peer Dependencies
|
|
708
|
+
|
|
709
|
+
This library requires the following peer dependencies:
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
yarn add react-native-fs \
|
|
713
|
+
react-native-image-crop-picker \
|
|
714
|
+
react-native-image-picker \
|
|
715
|
+
react-native-svg \
|
|
716
|
+
expo-modules-core
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
Or using npm:
|
|
720
|
+
|
|
721
|
+
```bash
|
|
722
|
+
npm install react-native-fs \
|
|
723
|
+
react-native-image-crop-picker \
|
|
724
|
+
react-native-image-picker \
|
|
725
|
+
react-native-svg \
|
|
726
|
+
expo-modules-core
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
**Optional (for image rotation features):**
|
|
730
|
+
|
|
731
|
+
```bash
|
|
732
|
+
# Choose one
|
|
733
|
+
yarn add expo-image-manipulator
|
|
734
|
+
# or
|
|
735
|
+
yarn add react-native-image-rotate
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### 2-1. Babel and Reanimated Setup (if needed)
|
|
739
|
+
|
|
740
|
+
If your project has a `babel.config.js` file, you may need the following plugins:
|
|
741
|
+
|
|
742
|
+
```javascript
|
|
743
|
+
module.exports = {
|
|
744
|
+
presets: ['module:@react-native/babel-preset'],
|
|
745
|
+
plugins: [
|
|
746
|
+
'react-native-reanimated/plugin' // Must be listed last
|
|
747
|
+
],
|
|
748
|
+
};
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
**Install additional packages if needed:**
|
|
752
|
+
|
|
753
|
+
```bash
|
|
754
|
+
yarn add react-native-reanimated
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### 3. iOS Setup
|
|
758
|
+
|
|
759
|
+
```bash
|
|
760
|
+
cd ios && pod install && cd ..
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
**Add Camera Permissions to Info.plist:**
|
|
764
|
+
|
|
765
|
+
Add the following permissions to your `ios/YourApp/Info.plist` file:
|
|
766
|
+
|
|
767
|
+
```xml
|
|
768
|
+
<key>NSCameraUsageDescription</key>
|
|
769
|
+
<string>We need camera access to scan documents</string>
|
|
770
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
771
|
+
<string>We need photo library access to save scanned documents</string>
|
|
772
|
+
<key>NSPhotoLibraryAddUsageDescription</key>
|
|
773
|
+
<string>We need photo library access to save scanned documents</string>
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
### 4. Android Setup
|
|
777
|
+
|
|
778
|
+
Android automatically links the native module. If you manage packages manually (legacy architecture), register `DocumentScannerPackage()` in your `MainApplication.java`.
|
|
779
|
+
|
|
780
|
+
**Permissions are automatically included:**
|
|
781
|
+
|
|
782
|
+
The following permissions are automatically included in the library's `AndroidManifest.xml`:
|
|
783
|
+
|
|
784
|
+
```xml
|
|
785
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
786
|
+
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
|
787
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
|
788
|
+
|
|
789
|
+
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
|
790
|
+
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
|
791
|
+
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
**Gradle Configuration:**
|
|
795
|
+
|
|
796
|
+
The library has the following minimum requirements:
|
|
797
|
+
- `minSdkVersion`: 21
|
|
798
|
+
- `compileSdkVersion`: 33
|
|
799
|
+
- `targetSdkVersion`: 33
|
|
800
|
+
- Kotlin: 1.8.21
|
|
801
|
+
- Java: 17
|
|
802
|
+
|
|
803
|
+
These are automatically applied, but make sure your project's `android/build.gradle` uses compatible versions.
|
|
804
|
+
|
|
805
|
+
**Example `android/build.gradle` configuration:**
|
|
806
|
+
|
|
807
|
+
```gradle
|
|
808
|
+
buildscript {
|
|
809
|
+
ext {
|
|
810
|
+
buildToolsVersion = "33.0.0"
|
|
811
|
+
minSdkVersion = 21
|
|
812
|
+
compileSdkVersion = 33
|
|
813
|
+
targetSdkVersion = 33
|
|
814
|
+
kotlinVersion = "1.8.21"
|
|
815
|
+
}
|
|
816
|
+
dependencies {
|
|
817
|
+
classpath("com.android.tools.build:gradle:7.4.2")
|
|
818
|
+
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
**Example `android/app/build.gradle` configuration:**
|
|
824
|
+
|
|
825
|
+
```gradle
|
|
826
|
+
android {
|
|
827
|
+
compileSdkVersion rootProject.ext.compileSdkVersion
|
|
828
|
+
|
|
829
|
+
compileOptions {
|
|
830
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
831
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
kotlinOptions {
|
|
835
|
+
jvmTarget = '17'
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
defaultConfig {
|
|
839
|
+
minSdkVersion rootProject.ext.minSdkVersion
|
|
840
|
+
targetSdkVersion rootProject.ext.targetSdkVersion
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
### 5. Automatic Quality Patch (Postinstall)
|
|
846
|
+
|
|
847
|
+
This library automatically optimizes camera quality through a **postinstall script**:
|
|
41
848
|
|
|
42
|
-
|
|
43
|
-
|
|
849
|
+
```bash
|
|
850
|
+
# Automatically runs on package installation
|
|
851
|
+
node scripts/postinstall.js
|
|
44
852
|
```
|
|
45
853
|
|
|
46
|
-
|
|
854
|
+
**What postinstall does:**
|
|
855
|
+
1. Locates the `react-native-document-scanner` package (auto-detected in node_modules)
|
|
856
|
+
2. Copies optimized iOS files from the vendor folder:
|
|
857
|
+
- `IPDFCameraViewController.m/h` - Uses AVCapturePhotoOutput
|
|
858
|
+
- `DocumentScannerView.m/h` - High quality settings
|
|
859
|
+
- `RNPdfScannerManager.m/h` - Native bridge
|
|
860
|
+
- `ios.js`, `index.js` - JavaScript interface
|
|
861
|
+
3. Original files are backed up with `.original` extension
|
|
862
|
+
|
|
863
|
+
**To run manually:**
|
|
864
|
+
|
|
865
|
+
```bash
|
|
866
|
+
npm run postinstall
|
|
867
|
+
# or
|
|
868
|
+
node scripts/postinstall.js
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
**Troubleshooting:**
|
|
872
|
+
- If postinstall fails, ensure `react-native-document-scanner` is installed
|
|
873
|
+
- When using yarn workspaces or monorepos, package hoisting may affect the path
|
|
874
|
+
|
|
875
|
+
### 6. Request Runtime Permissions
|
|
876
|
+
|
|
877
|
+
You need to request camera permissions at runtime in your app:
|
|
878
|
+
|
|
879
|
+
```typescript
|
|
880
|
+
import { PermissionsAndroid, Platform } from 'react-native';
|
|
881
|
+
|
|
882
|
+
async function requestCameraPermission() {
|
|
883
|
+
if (Platform.OS === 'android') {
|
|
884
|
+
try {
|
|
885
|
+
const granted = await PermissionsAndroid.request(
|
|
886
|
+
PermissionsAndroid.PERMISSIONS.CAMERA,
|
|
887
|
+
{
|
|
888
|
+
title: 'Camera Permission',
|
|
889
|
+
message: 'We need camera access to scan documents',
|
|
890
|
+
buttonNeutral: 'Ask Me Later',
|
|
891
|
+
buttonNegative: 'Cancel',
|
|
892
|
+
buttonPositive: 'OK',
|
|
893
|
+
}
|
|
894
|
+
);
|
|
895
|
+
return granted === PermissionsAndroid.RESULTS.GRANTED;
|
|
896
|
+
} catch (err) {
|
|
897
|
+
console.warn(err);
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return true;
|
|
902
|
+
}
|
|
903
|
+
```
|
|
47
904
|
|
|
48
905
|
## Usage
|
|
49
906
|
|
|
907
|
+
### Basic Example
|
|
908
|
+
|
|
50
909
|
```tsx
|
|
51
910
|
import React, { useRef } from 'react';
|
|
52
911
|
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
@@ -64,6 +923,7 @@ export const ScanScreen = () => {
|
|
|
64
923
|
minStableFrames={6}
|
|
65
924
|
onCapture={(result) => {
|
|
66
925
|
console.log('Captured document:', result.path);
|
|
926
|
+
console.log('Dimensions:', result.width, 'x', result.height);
|
|
67
927
|
}}
|
|
68
928
|
>
|
|
69
929
|
<View style={styles.overlay} pointerEvents="none">
|
|
@@ -74,13 +934,18 @@ export const ScanScreen = () => {
|
|
|
74
934
|
<TouchableOpacity
|
|
75
935
|
style={styles.captureButton}
|
|
76
936
|
onPress={() => scannerRef.current?.capture()}
|
|
77
|
-
|
|
937
|
+
>
|
|
938
|
+
<Text style={styles.captureButtonText}>Capture</Text>
|
|
939
|
+
</TouchableOpacity>
|
|
78
940
|
</View>
|
|
79
941
|
);
|
|
80
942
|
};
|
|
81
943
|
|
|
82
944
|
const styles = StyleSheet.create({
|
|
83
|
-
container: {
|
|
945
|
+
container: {
|
|
946
|
+
flex: 1,
|
|
947
|
+
backgroundColor: '#000'
|
|
948
|
+
},
|
|
84
949
|
overlay: {
|
|
85
950
|
position: 'absolute',
|
|
86
951
|
top: 60,
|
|
@@ -90,7 +955,10 @@ const styles = StyleSheet.create({
|
|
|
90
955
|
borderRadius: 12,
|
|
91
956
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
92
957
|
},
|
|
93
|
-
hint: {
|
|
958
|
+
hint: {
|
|
959
|
+
color: '#fff',
|
|
960
|
+
fontWeight: '600'
|
|
961
|
+
},
|
|
94
962
|
captureButton: {
|
|
95
963
|
position: 'absolute',
|
|
96
964
|
bottom: 40,
|
|
@@ -99,10 +967,18 @@ const styles = StyleSheet.create({
|
|
|
99
967
|
height: 70,
|
|
100
968
|
borderRadius: 35,
|
|
101
969
|
backgroundColor: '#fff',
|
|
970
|
+
justifyContent: 'center',
|
|
971
|
+
alignItems: 'center',
|
|
972
|
+
},
|
|
973
|
+
captureButtonText: {
|
|
974
|
+
color: '#000',
|
|
975
|
+
fontWeight: '600',
|
|
102
976
|
},
|
|
103
977
|
});
|
|
104
978
|
```
|
|
105
979
|
|
|
980
|
+
## Props
|
|
981
|
+
|
|
106
982
|
`<DocScanner />` passes through the important upstream props:
|
|
107
983
|
|
|
108
984
|
| Prop | Type | Default | Notes |
|
|
@@ -115,12 +991,262 @@ const styles = StyleSheet.create({
|
|
|
115
991
|
| `useBase64` | `boolean` | `false` | Return base64 payloads instead of file URIs. |
|
|
116
992
|
| `onCapture` | `(result) => void` | — | Receives `{ path, quad: null, width, height }`. |
|
|
117
993
|
|
|
994
|
+
### Manual Capture
|
|
995
|
+
|
|
118
996
|
Manual capture exposes an imperative `capture()` method via `ref`. Children render on top of the camera preview so you can build your own buttons, progress indicators, or onboarding tips.
|
|
119
997
|
|
|
120
998
|
## Convenience APIs
|
|
121
999
|
|
|
122
|
-
|
|
123
|
-
|
|
1000
|
+
### CropEditor
|
|
1001
|
+
|
|
1002
|
+
Wraps `react-native-perspective-image-cropper` for manual corner adjustment.
|
|
1003
|
+
|
|
1004
|
+
```tsx
|
|
1005
|
+
import { CropEditor } from 'react-native-rectangle-doc-scanner';
|
|
1006
|
+
|
|
1007
|
+
<CropEditor
|
|
1008
|
+
imagePath={capturedImagePath}
|
|
1009
|
+
onCropComplete={(croppedPath) => {
|
|
1010
|
+
console.log('Cropped image:', croppedPath);
|
|
1011
|
+
}}
|
|
1012
|
+
onCancel={() => {
|
|
1013
|
+
console.log('Crop cancelled');
|
|
1014
|
+
}}
|
|
1015
|
+
/>
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
### FullDocScanner
|
|
1019
|
+
|
|
1020
|
+
Puts the scanner and crop editor into a single modal-like flow. If the host app links either `expo-image-manipulator` or `react-native-image-rotate`, the confirmation screen exposes 90° rotation buttons; otherwise rotation controls remain hidden.
|
|
1021
|
+
|
|
1022
|
+
```tsx
|
|
1023
|
+
import { FullDocScanner } from 'react-native-rectangle-doc-scanner';
|
|
1024
|
+
|
|
1025
|
+
<FullDocScanner
|
|
1026
|
+
onComplete={(result) => {
|
|
1027
|
+
console.log('Completed:', result);
|
|
1028
|
+
}}
|
|
1029
|
+
onCancel={() => {
|
|
1030
|
+
console.log('Cancelled');
|
|
1031
|
+
}}
|
|
1032
|
+
/>
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
## Dependency Details
|
|
1036
|
+
|
|
1037
|
+
This library depends on various packages. Here's what each package does:
|
|
1038
|
+
|
|
1039
|
+
### Required Dependencies (Peer Dependencies)
|
|
1040
|
+
|
|
1041
|
+
| Package | Purpose | Required |
|
|
1042
|
+
|---------|---------|----------|
|
|
1043
|
+
| `react-native-fs` | File system access (save/read images) | ✅ Required |
|
|
1044
|
+
| `react-native-image-crop-picker` | Image selection and cropping | ✅ Required |
|
|
1045
|
+
| `react-native-image-picker` | Pick images from gallery/camera | ✅ Required |
|
|
1046
|
+
| `react-native-svg` | SVG rendering (UI overlays) | ✅ Required |
|
|
1047
|
+
| `expo-modules-core` | Expo module core functionality | ✅ Required |
|
|
1048
|
+
| `expo-image-manipulator` | Image rotation and editing | ⚙️ Optional (for rotation) |
|
|
1049
|
+
| `react-native-image-rotate` | Image rotation (alternative) | ⚙️ Optional (for rotation) |
|
|
1050
|
+
|
|
1051
|
+
### Internal Dependencies
|
|
1052
|
+
|
|
1053
|
+
| Package | Purpose |
|
|
1054
|
+
|---------|---------|
|
|
1055
|
+
| `react-native-document-scanner` | Native document scanner implementation (GitHub) |
|
|
1056
|
+
| `react-native-perspective-image-cropper` | Perspective correction crop editor |
|
|
1057
|
+
| `prop-types` | React PropTypes validation |
|
|
1058
|
+
|
|
1059
|
+
### Development Dependencies
|
|
1060
|
+
|
|
1061
|
+
| Package | Purpose |
|
|
1062
|
+
|---------|---------|
|
|
1063
|
+
| `typescript` | TypeScript compiler |
|
|
1064
|
+
| `@types/react` | React type definitions |
|
|
1065
|
+
| `@types/react-native` | React Native type definitions |
|
|
1066
|
+
| `@types/react-native-fs` | react-native-fs type definitions |
|
|
1067
|
+
|
|
1068
|
+
### Native Dependencies
|
|
1069
|
+
|
|
1070
|
+
**iOS (CocoaPods):**
|
|
1071
|
+
- OpenCV (image processing and document detection)
|
|
1072
|
+
- AVFoundation (camera API)
|
|
1073
|
+
- CoreImage (image filters and quality processing)
|
|
1074
|
+
|
|
1075
|
+
**Android (Gradle):**
|
|
1076
|
+
- OpenCV 4.9.0 (document detection)
|
|
1077
|
+
- CameraX 1.3.0 (camera API)
|
|
1078
|
+
- Kotlin Coroutines 1.7.3 (async processing)
|
|
1079
|
+
- ML Kit Document Scanner (document scanning)
|
|
1080
|
+
- ML Kit Object Detection (real-time rectangle detection)
|
|
1081
|
+
- AndroidX Core, AppCompat (Android base libraries)
|
|
1082
|
+
|
|
1083
|
+
## Tech Stack
|
|
1084
|
+
|
|
1085
|
+
### iOS
|
|
1086
|
+
- **Language**: Objective-C
|
|
1087
|
+
- **Camera API**: AVCapturePhotoOutput (iOS 10+)
|
|
1088
|
+
- **Image Processing**: OpenCV, CoreImage (CIContext)
|
|
1089
|
+
- **Minimum Version**: iOS 11.0
|
|
1090
|
+
- **Supported Architectures**: arm64, x86_64 (simulator)
|
|
1091
|
+
|
|
1092
|
+
### Android
|
|
1093
|
+
- **Language**: Kotlin 1.8.21
|
|
1094
|
+
- **Camera**: CameraX 1.3.0, Camera2 API
|
|
1095
|
+
- **Image Processing**: OpenCV 4.9.0
|
|
1096
|
+
- **ML Kit**: Document scanning and object detection
|
|
1097
|
+
- **Minimum SDK**: 21 (Android 5.0 Lollipop)
|
|
1098
|
+
- **Target SDK**: 33 (Android 13 Tiramisu)
|
|
1099
|
+
- **Java**: JDK 17
|
|
1100
|
+
- **Gradle**: 7.4.2+
|
|
1101
|
+
- **Android Gradle Plugin**: 7.4.2+
|
|
1102
|
+
|
|
1103
|
+
## Troubleshooting
|
|
1104
|
+
|
|
1105
|
+
### iOS Build Errors
|
|
1106
|
+
|
|
1107
|
+
**If you encounter build errors after pod install:**
|
|
1108
|
+
|
|
1109
|
+
```bash
|
|
1110
|
+
cd ios
|
|
1111
|
+
rm -rf Pods Podfile.lock
|
|
1112
|
+
pod cache clean --all
|
|
1113
|
+
pod install
|
|
1114
|
+
cd ..
|
|
1115
|
+
```
|
|
1116
|
+
|
|
1117
|
+
**"Module not found" or header file related errors:**
|
|
1118
|
+
|
|
1119
|
+
```bash
|
|
1120
|
+
# In Xcode: Product > Clean Build Folder (Shift + Cmd + K)
|
|
1121
|
+
# Or from terminal:
|
|
1122
|
+
cd ios
|
|
1123
|
+
xcodebuild clean -workspace YourApp.xcworkspace -scheme YourApp
|
|
1124
|
+
cd ..
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
**CocoaPods version issues:**
|
|
1128
|
+
|
|
1129
|
+
```bash
|
|
1130
|
+
sudo gem install cocoapods
|
|
1131
|
+
pod --version # Recommended 1.11.0+
|
|
1132
|
+
```
|
|
1133
|
+
|
|
1134
|
+
### Android Build Errors
|
|
1135
|
+
|
|
1136
|
+
**If you encounter Gradle build errors:**
|
|
1137
|
+
|
|
1138
|
+
```bash
|
|
1139
|
+
cd android
|
|
1140
|
+
./gradlew clean
|
|
1141
|
+
./gradlew --stop # Stop Gradle daemon
|
|
1142
|
+
cd ..
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
**Java version errors:**
|
|
1146
|
+
|
|
1147
|
+
This library requires Java 17. Check your Java version:
|
|
1148
|
+
|
|
1149
|
+
```bash
|
|
1150
|
+
java -version # Should show "17.x.x"
|
|
1151
|
+
```
|
|
1152
|
+
|
|
1153
|
+
**Kotlin version conflicts:**
|
|
1154
|
+
|
|
1155
|
+
Ensure Kotlin version in `android/build.gradle` is 1.8.21 or higher:
|
|
1156
|
+
|
|
1157
|
+
```gradle
|
|
1158
|
+
buildscript {
|
|
1159
|
+
ext.kotlin_version = '1.8.21'
|
|
1160
|
+
}
|
|
1161
|
+
```
|
|
1162
|
+
|
|
1163
|
+
**OpenCV dependency errors:**
|
|
1164
|
+
|
|
1165
|
+
If OpenCV doesn't download automatically:
|
|
1166
|
+
|
|
1167
|
+
```bash
|
|
1168
|
+
cd android
|
|
1169
|
+
./gradlew clean
|
|
1170
|
+
./gradlew :app:dependencies # Check dependencies
|
|
1171
|
+
cd ..
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
### Permission Errors
|
|
1175
|
+
|
|
1176
|
+
**If the camera is not working:**
|
|
1177
|
+
|
|
1178
|
+
1. **iOS**: Check that permission descriptions are added to Info.plist:
|
|
1179
|
+
- `NSCameraUsageDescription`
|
|
1180
|
+
- `NSPhotoLibraryUsageDescription`
|
|
1181
|
+
- `NSPhotoLibraryAddUsageDescription`
|
|
1182
|
+
|
|
1183
|
+
2. **Android**: Request runtime permissions using PermissionsAndroid:
|
|
1184
|
+
```typescript
|
|
1185
|
+
await PermissionsAndroid.request(
|
|
1186
|
+
PermissionsAndroid.PERMISSIONS.CAMERA
|
|
1187
|
+
);
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
3. Verify that camera permissions are granted in device settings
|
|
1191
|
+
|
|
1192
|
+
### Postinstall Script Errors
|
|
1193
|
+
|
|
1194
|
+
**If postinstall doesn't run:**
|
|
1195
|
+
|
|
1196
|
+
```bash
|
|
1197
|
+
# Run postinstall manually
|
|
1198
|
+
node node_modules/react-native-rectangle-doc-scanner/scripts/postinstall.js
|
|
1199
|
+
|
|
1200
|
+
# Or reinstall packages
|
|
1201
|
+
rm -rf node_modules
|
|
1202
|
+
yarn install # or npm install
|
|
1203
|
+
```
|
|
1204
|
+
|
|
1205
|
+
**"react-native-document-scanner not found" error:**
|
|
1206
|
+
|
|
1207
|
+
```bash
|
|
1208
|
+
# Verify react-native-document-scanner installation
|
|
1209
|
+
yarn add github:Michaelvilleneuve/react-native-document-scanner
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
### Metro Bundler Errors
|
|
1213
|
+
|
|
1214
|
+
**"Unable to resolve module" error:**
|
|
1215
|
+
|
|
1216
|
+
```bash
|
|
1217
|
+
# Clear Metro cache
|
|
1218
|
+
npx react-native start --reset-cache
|
|
1219
|
+
|
|
1220
|
+
# Or
|
|
1221
|
+
rm -rf $TMPDIR/metro-*
|
|
1222
|
+
rm -rf $TMPDIR/haste-*
|
|
1223
|
+
```
|
|
1224
|
+
|
|
1225
|
+
### Peer Dependencies Warning
|
|
1226
|
+
|
|
1227
|
+
**If you see "unmet peer dependency" warnings:**
|
|
1228
|
+
|
|
1229
|
+
Make sure all peer dependencies are installed:
|
|
1230
|
+
|
|
1231
|
+
```bash
|
|
1232
|
+
yarn add react-native-fs \
|
|
1233
|
+
react-native-image-crop-picker \
|
|
1234
|
+
react-native-image-picker \
|
|
1235
|
+
react-native-svg \
|
|
1236
|
+
expo-modules-core
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
### Expo Projects
|
|
1240
|
+
|
|
1241
|
+
If using Expo, some native modules may not work in Expo Go.
|
|
1242
|
+
Use a development build instead:
|
|
1243
|
+
|
|
1244
|
+
```bash
|
|
1245
|
+
npx expo prebuild
|
|
1246
|
+
npx expo run:ios
|
|
1247
|
+
# or
|
|
1248
|
+
npx expo run:android
|
|
1249
|
+
```
|
|
124
1250
|
|
|
125
1251
|
## License
|
|
126
1252
|
|
package/dist/FullDocScanner.js
CHANGED
|
@@ -540,12 +540,14 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
540
540
|
if (usesAndroidScannerActivity) {
|
|
541
541
|
startAndroidScan().catch((error) => {
|
|
542
542
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
543
|
-
|
|
544
|
-
if (errorMessage.includes('SCAN_CANCELLED')) {
|
|
543
|
+
if (errorMessage.includes('SCAN_CANCELLED') || errorMessage.includes('Document scan cancelled')) {
|
|
545
544
|
resetScannerView({ remount: true });
|
|
546
|
-
|
|
545
|
+
requestAnimationFrame(() => {
|
|
546
|
+
startAndroidScan().catch(() => null);
|
|
547
|
+
});
|
|
547
548
|
return;
|
|
548
549
|
}
|
|
550
|
+
console.error('[FullDocScanner] Android scan failed:', errorMessage, error);
|
|
549
551
|
emitError(error instanceof Error ? error : new Error(String(error)), 'Failed to capture image. Please try again.');
|
|
550
552
|
});
|
|
551
553
|
return;
|
|
@@ -579,26 +581,30 @@ const FullDocScanner = ({ onResult, onClose, detectionConfig, overlayColor = '#3
|
|
|
579
581
|
.catch((error) => {
|
|
580
582
|
clearTimeout(captureTimeout);
|
|
581
583
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
582
|
-
console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
|
|
583
584
|
captureModeRef.current = null;
|
|
584
585
|
captureInProgressRef.current = false;
|
|
585
|
-
if (errorMessage.includes('SCAN_CANCELLED')) {
|
|
586
|
+
if (errorMessage.includes('SCAN_CANCELLED') || errorMessage.includes('Document scan cancelled')) {
|
|
586
587
|
resetScannerView({ remount: true });
|
|
587
|
-
|
|
588
|
+
if (usesAndroidScannerActivity) {
|
|
589
|
+
requestAnimationFrame(() => {
|
|
590
|
+
startAndroidScan().catch(() => null);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
588
593
|
return;
|
|
589
594
|
}
|
|
595
|
+
console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
|
|
590
596
|
if (error instanceof Error && error.message !== 'capture_in_progress') {
|
|
591
597
|
emitError(error, 'Failed to capture image. Please try again.');
|
|
592
598
|
}
|
|
593
599
|
});
|
|
594
600
|
}, [
|
|
595
601
|
emitError,
|
|
596
|
-
onClose,
|
|
597
602
|
processing,
|
|
598
603
|
rectangleDetected,
|
|
599
604
|
rectangleHint,
|
|
600
605
|
captureReady,
|
|
601
606
|
resetScannerView,
|
|
607
|
+
startAndroidScan,
|
|
602
608
|
usesAndroidScannerActivity,
|
|
603
609
|
]);
|
|
604
610
|
const handleGalleryPick = (0, react_1.useCallback)(async () => {
|
package/package.json
CHANGED
package/src/FullDocScanner.tsx
CHANGED
|
@@ -720,12 +720,14 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
720
720
|
if (usesAndroidScannerActivity) {
|
|
721
721
|
startAndroidScan().catch((error: unknown) => {
|
|
722
722
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
723
|
-
|
|
724
|
-
if (errorMessage.includes('SCAN_CANCELLED')) {
|
|
723
|
+
if (errorMessage.includes('SCAN_CANCELLED') || errorMessage.includes('Document scan cancelled')) {
|
|
725
724
|
resetScannerView({ remount: true });
|
|
726
|
-
|
|
725
|
+
requestAnimationFrame(() => {
|
|
726
|
+
startAndroidScan().catch(() => null);
|
|
727
|
+
});
|
|
727
728
|
return;
|
|
728
729
|
}
|
|
730
|
+
console.error('[FullDocScanner] Android scan failed:', errorMessage, error);
|
|
729
731
|
emitError(
|
|
730
732
|
error instanceof Error ? error : new Error(String(error)),
|
|
731
733
|
'Failed to capture image. Please try again.',
|
|
@@ -769,16 +771,20 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
769
771
|
.catch((error: unknown) => {
|
|
770
772
|
clearTimeout(captureTimeout);
|
|
771
773
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
772
|
-
console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
|
|
773
774
|
captureModeRef.current = null;
|
|
774
775
|
captureInProgressRef.current = false;
|
|
775
776
|
|
|
776
|
-
if (errorMessage.includes('SCAN_CANCELLED')) {
|
|
777
|
+
if (errorMessage.includes('SCAN_CANCELLED') || errorMessage.includes('Document scan cancelled')) {
|
|
777
778
|
resetScannerView({ remount: true });
|
|
778
|
-
|
|
779
|
+
if (usesAndroidScannerActivity) {
|
|
780
|
+
requestAnimationFrame(() => {
|
|
781
|
+
startAndroidScan().catch(() => null);
|
|
782
|
+
});
|
|
783
|
+
}
|
|
779
784
|
return;
|
|
780
785
|
}
|
|
781
786
|
|
|
787
|
+
console.error('[FullDocScanner] Manual capture failed:', errorMessage, error);
|
|
782
788
|
if (error instanceof Error && error.message !== 'capture_in_progress') {
|
|
783
789
|
emitError(
|
|
784
790
|
error,
|
|
@@ -788,12 +794,12 @@ export const FullDocScanner: React.FC<FullDocScannerProps> = ({
|
|
|
788
794
|
});
|
|
789
795
|
}, [
|
|
790
796
|
emitError,
|
|
791
|
-
onClose,
|
|
792
797
|
processing,
|
|
793
798
|
rectangleDetected,
|
|
794
799
|
rectangleHint,
|
|
795
800
|
captureReady,
|
|
796
801
|
resetScannerView,
|
|
802
|
+
startAndroidScan,
|
|
797
803
|
usesAndroidScannerActivity,
|
|
798
804
|
]);
|
|
799
805
|
|