@capgo/capacitor-document-scanner 8.1.4 → 8.2.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 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> | Android only: maximum number of documents the user can scan. | <code>24</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, &gt;1 = more contrast, &lt;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, &gt;1 = more contrast, &lt;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
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 only: maximum number of documents the user can scan.",
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
- * Android only: maximum number of documents the user can scan.
21
- * @default 24
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":"AA2DA,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 * Android only: maximum number of documents the user can scan.\n * @default 24\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
+ {"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
- for pageNumber in 0 ..< scan.pageCount {
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.1.4"
6
+ private let pluginVersion: String = "8.2.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()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-document-scanner",
3
- "version": "8.1.4",
3
+ "version": "8.2.0",
4
4
  "description": "Capacitor plugin to scan document iOS and Android",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",