11package com.pspdfkit.react
22
3+ import android.net.Uri
4+ import com.facebook.react.bridge.Arguments
35import com.facebook.react.bridge.Promise
46import com.facebook.react.bridge.ReactApplicationContext
57import com.facebook.react.bridge.ReactContextBaseJavaModule
68import com.facebook.react.bridge.ReactMethod
9+ import com.facebook.react.bridge.ReadableArray
10+ import com.facebook.react.bridge.ReadableMap
711import 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
815import 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
929
1030@ReactModule(name = PDFDocumentModule .NAME )
1131class PDFDocumentModule (reactContext : ReactApplicationContext ) : ReactContextBaseJavaModule(reactContext) {
1232
1333 private var documents = mutableMapOf<Int , PdfDocument >()
34+ private var documentConfigurations = mutableMapOf<Int , MutableMap <String , Any >>()
1435
1536 override fun getName (): String {
1637 return NAME
@@ -20,14 +41,22 @@ class PDFDocumentModule(reactContext: ReactApplicationContext) : ReactContextBas
2041 return this .documents[reference]
2142 }
2243
44+ private fun getDocumentConfiguration (reference : Int ): MutableMap <String , Any >? {
45+ return this .documentConfigurations[reference]
46+ }
47+
2348 fun setDocument (document : PdfDocument , reference : Int ) {
2449 this .documents[reference] = document
2550 }
2651
52+ fun updateDocumentConfiguration (key : String , value : Any , reference : Int ) {
53+ val currentConfiguration = documentConfigurations[reference]
54+ currentConfiguration?.set(key, value)
55+ }
56+
2757 @ReactMethod fun getDocumentId (reference : Int , promise : Promise ) {
2858 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)
3160 } catch (e: Throwable ) {
3261 promise.reject(" getDocumentId error" , e)
3362 }
@@ -51,6 +80,242 @@ class PDFDocumentModule(reactContext: ReactApplicationContext) : ReactContextBas
5180 }
5281 }
5382
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+
54319 companion object {
55320 const val NAME = " PDFDocumentManager"
56321 }
0 commit comments