@capgo/capacitor-document-scanner 8.1.4 → 8.3.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 +9 -9
- package/android/src/main/java/app/capgo/plugin/document_scanner/DocumentScannerPlugin.java +4 -1
- package/dist/docs.json +2 -2
- package/dist/esm/definitions.d.ts +5 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/DocumentScannerPlugin/DocScanner.swift +71 -3
- package/ios/Sources/DocumentScannerPlugin/DocumentScannerPlugin.swift +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -74,15 +74,15 @@ Opens the device camera and starts the document scanning experience.
|
|
|
74
74
|
|
|
75
75
|
#### ScanDocumentOptions
|
|
76
76
|
|
|
77
|
-
| Prop | Type | Description | Default
|
|
78
|
-
| ------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
79
|
-
| **`croppedImageQuality`** | <code>number</code> | Android only: quality of the cropped image from 0 - 100 (100 is best). | <code>100</code>
|
|
80
|
-
| **`letUserAdjustCrop`** | <code>boolean</code> | Android only: allow the user to adjust the detected crop before saving. Disabling this forces single-document capture. | <code>true</code>
|
|
81
|
-
| **`maxNumDocuments`** | <code>number</code> |
|
|
82
|
-
| **`responseType`** | <code><a href="#responsetype">ResponseType</a></code> | Format to return scanned images in (file paths or base64 strings). | <code>ResponseType.ImageFilePath</code>
|
|
83
|
-
| **`brightness`** | <code>number</code> | Brightness adjustment applied to scanned images. Range: -255 to 255 (0 = no change, positive = brighter, negative = darker) Useful for compensating low-light scans. | <code>0</code>
|
|
84
|
-
| **`contrast`** | <code>number</code> | Contrast adjustment applied to scanned images. Range: 0.0 to 10.0 (1.0 = no change, >1 = more contrast, <1 = less contrast) Helps improve text clarity in poorly lit scans. | <code>1.0</code>
|
|
85
|
-
| **`scannerMode`** | <code><a href="#scannermode">ScannerMode</a></code> | Android only: scanner mode that controls ML Kit features and filters. - 'base': Basic scan with crop/rotate, no filters or ML cleaning - 'base_with_filter': Adds grayscale and auto-enhancement filters - 'full': All features including ML-based image cleaning (erases stains, fingers, etc.) | <code>ScannerMode.Full</code>
|
|
77
|
+
| Prop | Type | Description | Default |
|
|
78
|
+
| ------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------- |
|
|
79
|
+
| **`croppedImageQuality`** | <code>number</code> | Android only: quality of the cropped image from 0 - 100 (100 is best). | <code>100</code> |
|
|
80
|
+
| **`letUserAdjustCrop`** | <code>boolean</code> | Android only: allow the user to adjust the detected crop before saving. Disabling this forces single-document capture. | <code>true</code> |
|
|
81
|
+
| **`maxNumDocuments`** | <code>number</code> | Maximum number of documents to scan. On Android: limits documents the user can scan (1-24). On iOS: prevents scanning more than the specified number of pages (uses internal API swizzling). Set to 1 for single-scan mode where the scanner stops after one document. | <code>24 on Android, unlimited on iOS</code> |
|
|
82
|
+
| **`responseType`** | <code><a href="#responsetype">ResponseType</a></code> | Format to return scanned images in (file paths or base64 strings). | <code>ResponseType.ImageFilePath</code> |
|
|
83
|
+
| **`brightness`** | <code>number</code> | Brightness adjustment applied to scanned images. Range: -255 to 255 (0 = no change, positive = brighter, negative = darker) Useful for compensating low-light scans. | <code>0</code> |
|
|
84
|
+
| **`contrast`** | <code>number</code> | Contrast adjustment applied to scanned images. Range: 0.0 to 10.0 (1.0 = no change, >1 = more contrast, <1 = less contrast) Helps improve text clarity in poorly lit scans. | <code>1.0</code> |
|
|
85
|
+
| **`scannerMode`** | <code><a href="#scannermode">ScannerMode</a></code> | Android only: scanner mode that controls ML Kit features and filters. - 'base': Basic scan with crop/rotate, no filters or ML cleaning - 'base_with_filter': Adds grayscale and auto-enhancement filters - 'full': All features including ML-based image cleaning (erases stains, fingers, etc.) | <code>ScannerMode.Full</code> |
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
### Type Aliases
|
|
@@ -101,10 +101,13 @@ public class DocumentScannerPlugin extends Plugin {
|
|
|
101
101
|
int quality = clamp(call.getInt("croppedImageQuality", 100), 0, 100);
|
|
102
102
|
String responseType = normalizeResponseType(call.getString("responseType"));
|
|
103
103
|
int pageLimit = clamp(call.getInt("maxNumDocuments", 24), 1, 24);
|
|
104
|
-
boolean allowAdjustCrop = call.getBoolean("letUserAdjustCrop", true);
|
|
105
104
|
float brightness = clampFloat(call.getFloat("brightness", 0f), -255f, 255f);
|
|
106
105
|
float contrast = clampFloat(call.getFloat("contrast", 1f), 0f, 10f);
|
|
107
106
|
String scannerMode = normalizeScannerMode(call.getString("scannerMode"));
|
|
107
|
+
// Only default letUserAdjustCrop to true if scannerMode is FULL
|
|
108
|
+
// This ensures scannerMode takes precedence when explicitly set
|
|
109
|
+
boolean defaultAllowCrop = SCANNER_MODE_FULL.equals(scannerMode);
|
|
110
|
+
boolean allowAdjustCrop = call.getBoolean("letUserAdjustCrop", defaultAllowCrop);
|
|
108
111
|
|
|
109
112
|
GmsDocumentScannerOptions.Builder optionsBuilder = new GmsDocumentScannerOptions.Builder()
|
|
110
113
|
.setGalleryImportAllowed(false)
|
package/dist/docs.json
CHANGED
|
@@ -108,11 +108,11 @@
|
|
|
108
108
|
"name": "maxNumDocuments",
|
|
109
109
|
"tags": [
|
|
110
110
|
{
|
|
111
|
-
"text": "24",
|
|
111
|
+
"text": "24 on Android, unlimited on iOS",
|
|
112
112
|
"name": "default"
|
|
113
113
|
}
|
|
114
114
|
],
|
|
115
|
-
"docs": "Android
|
|
115
|
+
"docs": "Maximum number of documents to scan.\nOn Android: limits documents the user can scan (1-24).\nOn iOS: prevents scanning more than the specified number of pages (uses internal API swizzling).\nSet to 1 for single-scan mode where the scanner stops after one document.",
|
|
116
116
|
"complexTypes": [],
|
|
117
117
|
"type": "number | undefined"
|
|
118
118
|
},
|
|
@@ -17,8 +17,11 @@ export interface ScanDocumentOptions {
|
|
|
17
17
|
*/
|
|
18
18
|
letUserAdjustCrop?: boolean;
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Maximum number of documents to scan.
|
|
21
|
+
* On Android: limits documents the user can scan (1-24).
|
|
22
|
+
* On iOS: prevents scanning more than the specified number of pages (uses internal API swizzling).
|
|
23
|
+
* Set to 1 for single-scan mode where the scanner stops after one document.
|
|
24
|
+
* @default 24 on Android, unlimited on iOS
|
|
22
25
|
*/
|
|
23
26
|
maxNumDocuments?: number;
|
|
24
27
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AA8DA,MAAM,CAAN,IAAY,WAiBX;AAjBD,WAAY,WAAW;IACrB;;;OAGG;IACH,4BAAa,CAAA;IAEb;;OAEG;IACH,kDAAmC,CAAA;IAEnC;;;OAGG;IACH,4BAAa,CAAA;AACf,CAAC,EAjBW,WAAW,KAAX,WAAW,QAiBtB;AAED,MAAM,CAAN,IAAY,YAUX;AAVD,WAAY,YAAY;IACtB;;OAEG;IACH,iCAAiB,CAAA;IAEjB;;OAEG;IACH,+CAA+B,CAAA;AACjC,CAAC,EAVW,YAAY,KAAZ,YAAY,QAUvB;AAsBD,MAAM,CAAN,IAAY,0BAUX;AAVD,WAAY,0BAA0B;IACpC;;OAEG;IACH,iDAAmB,CAAA;IAEnB;;OAEG;IACH,+CAAiB,CAAA;AACnB,CAAC,EAVW,0BAA0B,KAA1B,0BAA0B,QAUrC","sourcesContent":["export interface DocumentScannerPlugin {\n /**\n * Opens the device camera and starts the document scanning experience.\n */\n scanDocument(options?: ScanDocumentOptions): Promise<ScanDocumentResponse>;\n}\n\nexport interface ScanDocumentOptions {\n /**\n * Android only: quality of the cropped image from 0 - 100 (100 is best).\n * @default 100\n */\n croppedImageQuality?: number;\n\n /**\n * Android only: allow the user to adjust the detected crop before saving.\n * Disabling this forces single-document capture.\n * @default true\n */\n letUserAdjustCrop?: boolean;\n\n /**\n * Maximum number of documents to scan.\n * On Android: limits documents the user can scan (1-24).\n * On iOS: prevents scanning more than the specified number of pages (uses internal API swizzling).\n * Set to 1 for single-scan mode where the scanner stops after one document.\n * @default 24 on Android, unlimited on iOS\n */\n maxNumDocuments?: number;\n\n /**\n * Format to return scanned images in (file paths or base64 strings).\n * @default ResponseType.ImageFilePath\n */\n responseType?: ResponseType;\n\n /**\n * Brightness adjustment applied to scanned images.\n * Range: -255 to 255 (0 = no change, positive = brighter, negative = darker)\n * Useful for compensating low-light scans.\n * @default 0\n */\n brightness?: number;\n\n /**\n * Contrast adjustment applied to scanned images.\n * Range: 0.0 to 10.0 (1.0 = no change, >1 = more contrast, <1 = less contrast)\n * Helps improve text clarity in poorly lit scans.\n * @default 1.0\n */\n contrast?: number;\n\n /**\n * Android only: scanner mode that controls ML Kit features and filters.\n * - 'base': Basic scan with crop/rotate, no filters or ML cleaning\n * - 'base_with_filter': Adds grayscale and auto-enhancement filters\n * - 'full': All features including ML-based image cleaning (erases stains, fingers, etc.)\n * @default ScannerMode.Full\n */\n scannerMode?: ScannerMode;\n}\n\nexport enum ScannerMode {\n /**\n * Basic document scanning with crop and rotate features only.\n * No filters or ML-based enhancements.\n */\n Base = 'base',\n\n /**\n * Basic features plus automatic filters (grayscale, auto-enhancement).\n */\n BaseWithFilter = 'base_with_filter',\n\n /**\n * Full feature set including ML-based image cleaning.\n * Automatically removes stains, fingers, and other artifacts.\n */\n Full = 'full',\n}\n\nexport enum ResponseType {\n /**\n * Return scanned images as base64-encoded strings.\n */\n Base64 = 'base64',\n\n /**\n * Return scanned images as file paths on disk.\n */\n ImageFilePath = 'imageFilePath',\n}\n\nexport interface ScanDocumentResponse {\n /**\n * Scanned images in the requested response format.\n */\n scannedImages?: string[];\n\n /**\n * Indicates whether the scan completed or was cancelled.\n */\n status?: ScanDocumentResponseStatus;\n\n /**\n * Get the native Capacitor plugin version\n *\n * @returns {Promise<{ id: string }>} an Promise with version for this device\n * @throws An error if the something went wrong\n */\n getPluginVersion(): Promise<{ version: string }>;\n}\n\nexport enum ScanDocumentResponseStatus {\n /**\n * The scan completed successfully.\n */\n Success = 'success',\n\n /**\n * The user cancelled the scan flow.\n */\n Cancel = 'cancel',\n}\n"]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import UIKit
|
|
2
2
|
import VisionKit
|
|
3
3
|
|
|
4
|
+
/// Global storage for maxNumDocuments limit (used by swizzled method)
|
|
5
|
+
private var documentScanLimit: Int?
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
Handles presenting the VisionKit document scanner and returning results.
|
|
6
9
|
*/
|
|
@@ -13,6 +16,9 @@ class DocScanner: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
|
13
16
|
private var croppedImageQuality: Int
|
|
14
17
|
private var brightness: Float
|
|
15
18
|
private var contrast: Float
|
|
19
|
+
private var maxNumDocuments: Int?
|
|
20
|
+
|
|
21
|
+
private static var swizzled = false
|
|
16
22
|
|
|
17
23
|
init(
|
|
18
24
|
_ viewController: UIViewController? = nil,
|
|
@@ -22,7 +28,8 @@ class DocScanner: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
|
22
28
|
responseType: String = ResponseType.imageFilePath,
|
|
23
29
|
croppedImageQuality: Int = 100,
|
|
24
30
|
brightness: Float = 0.0,
|
|
25
|
-
contrast: Float = 1.0
|
|
31
|
+
contrast: Float = 1.0,
|
|
32
|
+
maxNumDocuments: Int? = nil
|
|
26
33
|
) {
|
|
27
34
|
self.viewController = viewController
|
|
28
35
|
self.successHandler = successHandler
|
|
@@ -32,18 +39,74 @@ class DocScanner: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
|
32
39
|
self.croppedImageQuality = croppedImageQuality
|
|
33
40
|
self.brightness = brightness
|
|
34
41
|
self.contrast = contrast
|
|
42
|
+
self.maxNumDocuments = maxNumDocuments
|
|
35
43
|
}
|
|
36
44
|
|
|
37
45
|
override convenience init() {
|
|
38
46
|
self.init(nil)
|
|
39
47
|
}
|
|
40
48
|
|
|
49
|
+
/// Swizzle the internal canAddImages method to enforce document limits
|
|
50
|
+
private static func setupSwizzling() {
|
|
51
|
+
guard !swizzled else { return }
|
|
52
|
+
swizzled = true
|
|
53
|
+
|
|
54
|
+
// Find the internal VNDocumentCameraViewController_InProcess class
|
|
55
|
+
guard let inProcessClass = NSClassFromString("VNDocumentCameraViewController_InProcess") else {
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Selector for the internal delegate method: documentCameraController:canAddImages:
|
|
60
|
+
let originalSelector = NSSelectorFromString("documentCameraController:canAddImages:")
|
|
61
|
+
let swizzledSelector = #selector(DocScanner.swizzled_documentCameraController(_:canAddImages:))
|
|
62
|
+
|
|
63
|
+
guard let originalMethod = class_getInstanceMethod(inProcessClass, originalSelector),
|
|
64
|
+
let swizzledMethod = class_getInstanceMethod(DocScanner.self, swizzledSelector) else {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add the swizzled method to the target class
|
|
69
|
+
let didAdd = class_addMethod(
|
|
70
|
+
inProcessClass,
|
|
71
|
+
swizzledSelector,
|
|
72
|
+
method_getImplementation(swizzledMethod),
|
|
73
|
+
method_getTypeEncoding(swizzledMethod)
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if didAdd {
|
|
77
|
+
guard let newSwizzledMethod = class_getInstanceMethod(inProcessClass, swizzledSelector) else {
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
method_exchangeImplementations(originalMethod, newSwizzledMethod)
|
|
81
|
+
} else {
|
|
82
|
+
method_exchangeImplementations(originalMethod, swizzledMethod)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Swizzled implementation that enforces document limits
|
|
87
|
+
@objc dynamic func swizzled_documentCameraController(_ controller: AnyObject, canAddImages count: UInt64) -> Bool {
|
|
88
|
+
// Check if we have a limit set
|
|
89
|
+
if let limit = documentScanLimit, count >= limit {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
// Call the original implementation (swizzled, so this calls original)
|
|
93
|
+
return swizzled_documentCameraController(controller, canAddImages: count)
|
|
94
|
+
}
|
|
95
|
+
|
|
41
96
|
func startScan() {
|
|
42
97
|
guard VNDocumentCameraViewController.isSupported else {
|
|
43
98
|
errorHandler("Document scanning is not supported on this device.")
|
|
44
99
|
return
|
|
45
100
|
}
|
|
46
101
|
|
|
102
|
+
// Set the global limit and setup swizzling if we have a limit
|
|
103
|
+
if let limit = maxNumDocuments, limit > 0 {
|
|
104
|
+
documentScanLimit = limit
|
|
105
|
+
DocScanner.setupSwizzling()
|
|
106
|
+
} else {
|
|
107
|
+
documentScanLimit = nil
|
|
108
|
+
}
|
|
109
|
+
|
|
47
110
|
DispatchQueue.main.async {
|
|
48
111
|
let documentCameraViewController = VNDocumentCameraViewController()
|
|
49
112
|
documentCameraViewController.delegate = self
|
|
@@ -59,7 +122,8 @@ class DocScanner: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
|
59
122
|
responseType: String? = ResponseType.imageFilePath,
|
|
60
123
|
croppedImageQuality: Int? = 100,
|
|
61
124
|
brightness: Float? = 0.0,
|
|
62
|
-
contrast: Float? = 1.0
|
|
125
|
+
contrast: Float? = 1.0,
|
|
126
|
+
maxNumDocuments: Int? = nil
|
|
63
127
|
) {
|
|
64
128
|
self.viewController = viewController
|
|
65
129
|
self.successHandler = successHandler
|
|
@@ -69,6 +133,7 @@ class DocScanner: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
|
69
133
|
self.croppedImageQuality = croppedImageQuality ?? 100
|
|
70
134
|
self.brightness = brightness ?? 0.0
|
|
71
135
|
self.contrast = contrast ?? 1.0
|
|
136
|
+
self.maxNumDocuments = maxNumDocuments
|
|
72
137
|
|
|
73
138
|
startScan()
|
|
74
139
|
}
|
|
@@ -79,7 +144,10 @@ class DocScanner: NSObject, VNDocumentCameraViewControllerDelegate {
|
|
|
79
144
|
) {
|
|
80
145
|
var results: [String] = []
|
|
81
146
|
|
|
82
|
-
|
|
147
|
+
// Limit pages to maxNumDocuments if specified
|
|
148
|
+
let pageLimit = maxNumDocuments != nil ? min(scan.pageCount, maxNumDocuments!) : scan.pageCount
|
|
149
|
+
|
|
150
|
+
for pageNumber in 0 ..< pageLimit {
|
|
83
151
|
var processedImage = scan.imageOfPage(at: pageNumber)
|
|
84
152
|
|
|
85
153
|
// Apply brightness and contrast adjustments if needed
|
|
@@ -3,7 +3,7 @@ import Foundation
|
|
|
3
3
|
|
|
4
4
|
@objc(DocumentScannerPlugin)
|
|
5
5
|
public class DocumentScannerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
6
|
-
private let pluginVersion: String = "8.
|
|
6
|
+
private let pluginVersion: String = "8.3.0"
|
|
7
7
|
public let identifier = "DocumentScannerPlugin"
|
|
8
8
|
public let jsName = "DocumentScanner"
|
|
9
9
|
public let pluginMethods: [CAPPluginMethod] = [
|
|
@@ -41,7 +41,8 @@ public class DocumentScannerPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
41
41
|
responseType: call.getString("responseType") ?? ResponseType.imageFilePath,
|
|
42
42
|
croppedImageQuality: clampQuality(call.getInt("croppedImageQuality")),
|
|
43
43
|
brightness: clampBrightness(call.getFloat("brightness")),
|
|
44
|
-
contrast: clampContrast(call.getFloat("contrast"))
|
|
44
|
+
contrast: clampContrast(call.getFloat("contrast")),
|
|
45
|
+
maxNumDocuments: call.getInt("maxNumDocuments")
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
documentScanner?.startScan()
|