1
1
package com.pspdfkit.react
2
2
3
+ import android.net.Uri
4
+ import com.facebook.react.bridge.Arguments
3
5
import com.facebook.react.bridge.Promise
4
6
import com.facebook.react.bridge.ReactApplicationContext
5
7
import com.facebook.react.bridge.ReactContextBaseJavaModule
6
8
import com.facebook.react.bridge.ReactMethod
9
+ import com.facebook.react.bridge.ReadableArray
10
+ import com.facebook.react.bridge.ReadableMap
7
11
import com.facebook.react.module.annotations.ReactModule
12
+ import com.pspdfkit.annotations.Annotation
13
+ import com.pspdfkit.annotations.AnnotationProvider.ALL_ANNOTATION_TYPES
14
+ import com.pspdfkit.annotations.AnnotationType
8
15
import com.pspdfkit.document.PdfDocument
16
+ import com.pspdfkit.document.formatters.DocumentJsonFormatter
17
+ import com.pspdfkit.document.formatters.XfdfFormatter
18
+ import com.pspdfkit.document.providers.ContentResolverDataProvider
19
+ import com.pspdfkit.document.providers.DataProvider
20
+ import com.pspdfkit.internal.model.ImageDocumentImpl
21
+ import com.pspdfkit.react.helper.ConversionHelpers.getAnnotationTypeFromString
22
+ import com.pspdfkit.react.helper.DocumentJsonDataProvider
23
+ import com.pspdfkit.react.helper.JsonUtilities
24
+ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
25
+ import io.reactivex.rxjava3.schedulers.Schedulers
26
+ import org.json.JSONObject
27
+ import java.io.ByteArrayOutputStream
28
+ import java.util.EnumSet
9
29
10
30
@ReactModule(name = PDFDocumentModule .NAME )
11
31
class PDFDocumentModule (reactContext : ReactApplicationContext ) : ReactContextBaseJavaModule(reactContext) {
12
32
13
33
private var documents = mutableMapOf<Int , PdfDocument >()
34
+ private var documentConfigurations = mutableMapOf<Int , MutableMap <String , Any >>()
14
35
15
36
override fun getName (): String {
16
37
return NAME
@@ -20,14 +41,22 @@ class PDFDocumentModule(reactContext: ReactApplicationContext) : ReactContextBas
20
41
return this .documents[reference]
21
42
}
22
43
44
+ private fun getDocumentConfiguration (reference : Int ): MutableMap <String , Any >? {
45
+ return this .documentConfigurations[reference]
46
+ }
47
+
23
48
fun setDocument (document : PdfDocument , reference : Int ) {
24
49
this .documents[reference] = document
25
50
}
26
51
52
+ fun updateDocumentConfiguration (key : String , value : Any , reference : Int ) {
53
+ val currentConfiguration = documentConfigurations[reference]
54
+ currentConfiguration?.set(key, value)
55
+ }
56
+
27
57
@ReactMethod fun getDocumentId (reference : Int , promise : Promise ) {
28
58
try {
29
- // Using uid here until Android exposes the documentId property.
30
- promise.resolve(this .getDocument(reference)?.uid)
59
+ promise.resolve(this .getDocument(reference)?.documentIdString)
31
60
} catch (e: Throwable ) {
32
61
promise.reject(" getDocumentId error" , e)
33
62
}
@@ -51,6 +80,242 @@ class PDFDocumentModule(reactContext: ReactApplicationContext) : ReactContextBas
51
80
}
52
81
}
53
82
83
+ @ReactMethod fun save (reference : Int , promise : Promise ) {
84
+ try {
85
+ this .getDocument(reference)?.let {
86
+ if (it is ImageDocumentImpl .ImagePdfDocumentWrapper ) {
87
+ val metadata = this .getDocumentConfiguration(reference)?.get(" imageSaveMode" )?.equals(" flattenAndEmbed" ) == true
88
+ if (it.imageDocument.saveIfModified(metadata)) {
89
+ promise.resolve(true )
90
+ }
91
+ } else {
92
+ it.saveIfModified()
93
+ promise.resolve(true )
94
+ }
95
+ }
96
+ promise.reject(" save error" , RuntimeException (" Could not save document" ))
97
+ } catch (e: Throwable ) {
98
+ promise.reject(" save error" , e)
99
+ }
100
+ }
101
+
102
+ @ReactMethod fun getAllUnsavedAnnotations (reference : Int , promise : Promise ) {
103
+ try {
104
+ this .getDocument(reference)?.let {
105
+ val outputStream = ByteArrayOutputStream ()
106
+ DocumentJsonFormatter .exportDocumentJsonAsync(it, outputStream)
107
+ .subscribeOn(Schedulers .io())
108
+ .observeOn(AndroidSchedulers .mainThread())
109
+ .subscribe(
110
+ {
111
+ val json = JSONObject (outputStream.toString())
112
+ val jsonMap = JsonUtilities .jsonObjectToMap(json)
113
+ val nativeMap = Arguments .makeNativeMap(jsonMap)
114
+ promise.resolve(nativeMap)
115
+ }, { e ->
116
+ promise.reject(RuntimeException (e))
117
+ }
118
+ )
119
+ }
120
+ } catch (e: Throwable ) {
121
+ promise.reject(" getAllUnsavedAnnotations error" , e)
122
+ }
123
+ }
124
+
125
+ @ReactMethod fun getAnnotations (reference : Int , type : String? , promise : Promise ) {
126
+ try {
127
+ this .getDocument(reference)?.let {
128
+ it.annotationProvider.getAllAnnotationsOfTypeAsync(if (type == null ) ALL_ANNOTATION_TYPES else getAnnotationTypeFromString(type))
129
+ .toList()
130
+ .subscribeOn(Schedulers .io())
131
+ .observeOn(AndroidSchedulers .mainThread())
132
+ .subscribe(
133
+ { annotations ->
134
+ var annotationsSerialized: ArrayList <Map <String , Any >> = ArrayList ()
135
+ for (annotation in annotations) {
136
+ if (annotation.type == AnnotationType .POPUP ) {
137
+ continue
138
+ }
139
+ val annotationInstantJSON = JSONObject (annotation.toInstantJson())
140
+ val annotationMap = JsonUtilities .jsonObjectToMap(annotationInstantJSON)
141
+ annotationMap[" uuid" ] = annotation.uuid
142
+ annotationsSerialized.add(annotationMap)
143
+ }
144
+ val nativeList = Arguments .makeNativeArray(annotationsSerialized)
145
+ promise.resolve(nativeList)
146
+ }, { e ->
147
+ promise.reject(RuntimeException (e))
148
+ }
149
+ )
150
+ }
151
+ } catch (e: Throwable ) {
152
+ promise.reject(" getAnnotations error" , e)
153
+ }
154
+ }
155
+
156
+ @ReactMethod fun getAnnotationsForPage (reference : Int , pageIndex : Int , type : String? , promise : Promise ) {
157
+ try {
158
+ this .getDocument(reference)?.let {
159
+
160
+ if (pageIndex > it.pageCount- 1 ) {
161
+ promise.reject(RuntimeException (" Specified page index is out of bounds" ))
162
+ return
163
+ }
164
+
165
+ it.annotationProvider.getAllAnnotationsOfTypeAsync(if (type == null ) EnumSet .allOf(AnnotationType ::class .java) else
166
+ getAnnotationTypeFromString(type), pageIndex, 1 )
167
+ .toList()
168
+ .subscribeOn(Schedulers .io())
169
+ .observeOn(AndroidSchedulers .mainThread())
170
+ .subscribe(
171
+ { annotations ->
172
+ var annotationsSerialized: ArrayList <Map <String , Any >> = ArrayList ()
173
+ for (annotation in annotations) {
174
+ if (annotation.type == AnnotationType .POPUP ) {
175
+ continue
176
+ }
177
+ val annotationInstantJSON = JSONObject (annotation.toInstantJson())
178
+ val annotationMap = JsonUtilities .jsonObjectToMap(annotationInstantJSON)
179
+ annotationMap[" uuid" ] = annotation.uuid
180
+ annotationsSerialized.add(annotationMap)
181
+ }
182
+ val nativeList = Arguments .makeNativeArray(annotationsSerialized)
183
+ promise.resolve(nativeList)
184
+ }, { e ->
185
+ promise.reject(RuntimeException (e))
186
+ }
187
+ )
188
+ }
189
+ } catch (e: Throwable ) {
190
+ promise.reject(" getAnnotationsForPage error" , e)
191
+ }
192
+ }
193
+
194
+ @ReactMethod fun removeAnnotations (reference : Int , instantJSON : ReadableArray , promise : Promise ) {
195
+ try {
196
+ this .getDocument(reference)?.let {
197
+
198
+ val instantJSONArray: List <Map <String , Any >> = instantJSON.toArrayList().filterIsInstance<Map <String , Any >>()
199
+ var annotationsToDelete: ArrayList <Annotation > = ArrayList ()
200
+ it.annotationProvider.getAllAnnotationsOfTypeAsync(ALL_ANNOTATION_TYPES )
201
+ .toList()
202
+ .subscribeOn(Schedulers .io())
203
+ .observeOn(AndroidSchedulers .mainThread())
204
+ .subscribe(
205
+ { annotations ->
206
+ for (annotation in annotations) {
207
+ for (instantJSONAnnotation in instantJSONArray) {
208
+ if (annotation.name == instantJSONAnnotation[" name" ] ||
209
+ annotation.uuid == instantJSONAnnotation[" uuid" ]) {
210
+ annotationsToDelete.add(annotation)
211
+ }
212
+ }
213
+ }
214
+
215
+ for (annotation in annotationsToDelete) {
216
+ it.annotationProvider.removeAnnotationFromPage(annotation)
217
+ }
218
+ promise.resolve(true )
219
+ }, { e ->
220
+ promise.reject(RuntimeException (e))
221
+ }
222
+ )
223
+ }
224
+ } catch (e: Throwable ) {
225
+ promise.reject(" removeAnnotations error" , e)
226
+ }
227
+ }
228
+
229
+ @ReactMethod fun addAnnotations (reference : Int , instantJSON : ReadableMap , promise : Promise ) {
230
+ try {
231
+ this .getDocument(reference)?.let {
232
+ val json = JSONObject (instantJSON.toHashMap())
233
+ val dataProvider: DataProvider = DocumentJsonDataProvider (json)
234
+ DocumentJsonFormatter .importDocumentJsonAsync(it, dataProvider)
235
+ .subscribeOn(Schedulers .io())
236
+ .observeOn(AndroidSchedulers .mainThread())
237
+ .subscribe({
238
+ promise.resolve(true )
239
+ }, { e ->
240
+ promise.reject(RuntimeException (e))
241
+ })
242
+ }
243
+ } catch (e: Throwable ) {
244
+ promise.reject(" addAnnotations error" , e)
245
+ }
246
+ }
247
+
248
+ @ReactMethod fun importXFDF (reference : Int , filePath : String , promise : Promise ) {
249
+ try {
250
+ this .getDocument(reference)?.let {
251
+ var importPath = filePath;
252
+ if (Uri .parse(importPath).scheme == null ) {
253
+ importPath = " file:///$filePath " ;
254
+ }
255
+
256
+ XfdfFormatter .parseXfdfAsync(it, ContentResolverDataProvider ((Uri .parse(importPath))))
257
+ .subscribeOn(Schedulers .io())
258
+ .observeOn(AndroidSchedulers .mainThread())
259
+ .subscribe(
260
+ { annotations ->
261
+ for (annotation in annotations) {
262
+ it.annotationProvider.addAnnotationToPage(annotation)
263
+ }
264
+ val result = JSONObject ()
265
+ result.put(" success" , true )
266
+ val jsonMap = JsonUtilities .jsonObjectToMap(result)
267
+ val nativeMap = Arguments .makeNativeMap(jsonMap)
268
+ promise.resolve(nativeMap)
269
+ }, { e ->
270
+ promise.reject(" importXFDF error" , e)
271
+ })
272
+ }
273
+ } catch (e: Throwable ) {
274
+ promise.reject(" importXFDF error" , e)
275
+ }
276
+ }
277
+
278
+ @ReactMethod fun exportXFDF (reference : Int , filePath : String , promise : Promise ) {
279
+ try {
280
+ this .getDocument(reference)?.let {
281
+ var exportPath = filePath;
282
+ if (Uri .parse(exportPath).scheme == null ) {
283
+ exportPath = " file:///$filePath " ;
284
+ }
285
+
286
+ val outputStream = reactApplicationContext.contentResolver.openOutputStream(Uri .parse(exportPath))
287
+ if (outputStream == null ) {
288
+ promise.reject(" exportXFDF error" , RuntimeException (" Could not write to supplied file path error" ))
289
+ return
290
+ }
291
+
292
+ val allAnnotations = it.annotationProvider.getAllAnnotationsOfType(ALL_ANNOTATION_TYPES )
293
+ val allFormFields = it.formProvider.formFields
294
+
295
+ XfdfFormatter .writeXfdfAsync(it, allAnnotations, allFormFields, outputStream)
296
+ XfdfFormatter .parseXfdfAsync(it, ContentResolverDataProvider ((Uri .parse(exportPath))))
297
+ .subscribeOn(Schedulers .io())
298
+ .observeOn(AndroidSchedulers .mainThread())
299
+ .subscribe(
300
+ { annotations ->
301
+ for (annotation in annotations) {
302
+ it.annotationProvider.addAnnotationToPage(annotation)
303
+ }
304
+ val result = JSONObject ()
305
+ result.put(" success" , true )
306
+ result.put(" filePath" , filePath)
307
+ val jsonMap = JsonUtilities .jsonObjectToMap(result)
308
+ val nativeMap = Arguments .makeNativeMap(jsonMap)
309
+ promise.resolve(nativeMap)
310
+ }, { e ->
311
+ promise.reject(" exportXFDF error" , e)
312
+ })
313
+ }
314
+ } catch (e: Throwable ) {
315
+ promise.reject(" exportXFDF error" , e)
316
+ }
317
+ }
318
+
54
319
companion object {
55
320
const val NAME = " PDFDocumentManager"
56
321
}
0 commit comments