diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d2bc924..dbc750f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ ## Newest Release +### 2.11.0 - 07 Jun 2024 + +- Adds the ability to clear the document cache. (J#HYB-347) +- Adds support for opening PDF documents from a remote URL. (J#HYB-354) +- Updates `setAnnotationFlags` and `getAnnotationFlags` APIs to support using annotation `name` as an identifier. (J#HYB-372) +- Fixes an issue where calling `exitCurrentlyActiveMode` while not in annotation editing mode generates an exception on iOS. (J#HYB-373) +- Fixes an issue where the annotation `uuid` isn't included in `onAnnotationTapped` callbacks. (J#HYB-374) +- Fixes an issue where Instant configuration wasn't applied when using the `presentInstant` API on iOS. (J#HYB-375) + +## Previous Releases + ### 2.10.0 - 06 May 2024 - Adds the ability to define annotation behavior using flags. (J#HYB-283) @@ -9,8 +20,6 @@ - Fixes an issue where selecting a measurement annotation without the Measurement Tools license causes a crash. (J#HYB-318) - Fixes an issue where the `removeAnnotation` API sometimes failed to remove an annotation on iOS. (J#HYB-43) -## Previous Releases - ### 2.9.1 - 12 Apr 2024 - Adds the ability to import and export annotations from XFDF files. (J#HYB-293) diff --git a/License-Evaluation.pdf b/License-Evaluation.pdf index 553b2ce8..4d97e8cb 100644 Binary files a/License-Evaluation.pdf and b/License-Evaluation.pdf differ diff --git a/android/src/main/java/com/pspdfkit/react/PDFDocumentModule.kt b/android/src/main/java/com/pspdfkit/react/PDFDocumentModule.kt new file mode 100644 index 00000000..21f6972f --- /dev/null +++ b/android/src/main/java/com/pspdfkit/react/PDFDocumentModule.kt @@ -0,0 +1,57 @@ +package com.pspdfkit.react + +import com.facebook.react.bridge.Promise +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.module.annotations.ReactModule +import com.pspdfkit.document.PdfDocument + +@ReactModule(name = PDFDocumentModule.NAME) +class PDFDocumentModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { + + private var documents = mutableMapOf() + + override fun getName(): String { + return NAME + } + + private fun getDocument(reference: Int): PdfDocument? { + return this.documents[reference] + } + + fun setDocument(document: PdfDocument, reference: Int) { + this.documents[reference] = document + } + + @ReactMethod fun getDocumentId(reference: Int, promise: Promise) { + try { + // Using uid here until Android exposes the documentId property. + promise.resolve(this.getDocument(reference)?.uid) + } catch (e: Throwable) { + promise.reject("getDocumentId error", e) + } + } + + @ReactMethod fun invalidateCacheForPage(reference: Int, pageIndex: Int, promise: Promise) { + try { + this.getDocument(reference)?.invalidateCacheForPage(pageIndex) + promise.resolve(true) + } catch (e: Throwable) { + promise.reject("invalidateCacheForPage error", e) + } + } + + @ReactMethod fun invalidateCache(reference: Int, promise: Promise) { + try { + this.getDocument(reference)?.invalidateCache() + promise.resolve(true) + } catch (e: Throwable) { + promise.reject("invalidateCache error", e) + } + } + + companion object { + const val NAME = "PDFDocumentManager" + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/react/PSPDFKitModule.java b/android/src/main/java/com/pspdfkit/react/PSPDFKitModule.java index 21ed8163..866829b1 100644 --- a/android/src/main/java/com/pspdfkit/react/PSPDFKitModule.java +++ b/android/src/main/java/com/pspdfkit/react/PSPDFKitModule.java @@ -107,11 +107,10 @@ public String getName() { @ReactMethod public void present(@NonNull String document, @NonNull ReadableMap configuration, @Nullable Promise promise) { - File documentFile = new File(document); - if(PSPDFKitUtils.isValidPdf(documentFile)) { + if(PSPDFKitUtils.isValidPdf(document)) { lastPresentPromise = promise; presentPdf(document, configuration, promise); - } else if(PSPDFKitUtils.isValidImage(documentFile)) { + } else if(PSPDFKitUtils.isValidImage(document)) { lastPresentPromise = promise; presentImage(document, configuration, promise); }else { diff --git a/android/src/main/java/com/pspdfkit/react/PSPDFKitPackage.java b/android/src/main/java/com/pspdfkit/react/PSPDFKitPackage.java index df7e4148..c7eceb6c 100644 --- a/android/src/main/java/com/pspdfkit/react/PSPDFKitPackage.java +++ b/android/src/main/java/com/pspdfkit/react/PSPDFKitPackage.java @@ -33,13 +33,14 @@ public List createNativeModules(ReactApplicationContext reactConte modules.add(new PSPDFKitModule(reactContext)); modules.add(new TestingModule(reactContext)); modules.add(new RNProcessor(reactContext)); + modules.add(new PDFDocumentModule(reactContext)); return modules; } @Override public List createViewManagers(ReactApplicationContext reactContext) { List viewManagers = new ArrayList<>(); - viewManagers.add(new ReactPdfViewManager()); + viewManagers.add(new ReactPdfViewManager(reactContext)); return viewManagers; } } diff --git a/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java b/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java index cd357572..eb66165b 100644 --- a/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java +++ b/android/src/main/java/com/pspdfkit/react/ReactPdfViewManager.java @@ -18,6 +18,7 @@ import androidx.annotation.NonNull; import androidx.fragment.app.FragmentActivity; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableArray; @@ -78,6 +79,12 @@ public class ReactPdfViewManager extends ViewGroupManager { private final CompositeDisposable annotationDisposables = new CompositeDisposable(); + private final ReactApplicationContext reactApplicationContext; + + public ReactPdfViewManager(ReactApplicationContext reactApplicationContext) { + this.reactApplicationContext = reactApplicationContext; + } + @NonNull @Override public String getName() { @@ -152,13 +159,14 @@ public void setConfiguration(PdfView view, @NonNull ReadableMap configuration) { view.setConfiguration(configurationBuild); } view.setDocumentPassword(configuration.getString("documentPassword")); + view.setRemoteDocumentConfiguration(configuration.getMap("remoteDocumentConfiguration")); // Although MeasurementValueConfigurations is specified as part of Configuration, it is configured separately on the Android SDK if (configuration.getArray("measurementValueConfigurations") != null) { view.setMeasurementValueConfigurations(configuration.getArray("measurementValueConfigurations")); - } + } } - @ReactProp(name = "annotationPresets") + @ReactProp(name = "annotationPresets") public void setAnnotationPresets(PdfView view, @NonNull ReadableMap annotationPresets) { Map annotationsConfiguration = AnnotationConfigurationAdaptor.convertAnnotationConfigurations( view.getContext(), annotationPresets @@ -168,7 +176,7 @@ public void setAnnotationPresets(PdfView view, @NonNull ReadableMap annotationPr @ReactProp(name = "document") public void setDocument(PdfView view, @NonNull String document) { - view.setDocument(document); + view.setDocument(document, this.reactApplicationContext); } @ReactProp(name = "pageIndex") diff --git a/android/src/main/java/com/pspdfkit/react/events/CustomAnnotationContextualMenuItemTappedEvent.kt b/android/src/main/java/com/pspdfkit/react/events/CustomAnnotationContextualMenuItemTappedEvent.kt index 69de2892..d08d9299 100644 --- a/android/src/main/java/com/pspdfkit/react/events/CustomAnnotationContextualMenuItemTappedEvent.kt +++ b/android/src/main/java/com/pspdfkit/react/events/CustomAnnotationContextualMenuItemTappedEvent.kt @@ -13,7 +13,7 @@ class CustomAnnotationContextualMenuItemTappedEvent: Event { this.buttonId = buttonId } - override fun getEventName(): String? { + override fun getEventName(): String { return EVENT_NAME } diff --git a/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationTappedEvent.java b/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationTappedEvent.java index ed18dedb..3ba47c37 100644 --- a/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationTappedEvent.java +++ b/android/src/main/java/com/pspdfkit/react/events/PdfViewAnnotationTappedEvent.java @@ -55,6 +55,7 @@ public void dispatch(RCTEventEmitter rctEventEmitter) { if (rawInstantJson != null && !rawInstantJson.equals("null")) { JSONObject instantJson = new JSONObject(rawInstantJson); Map map = JsonUtilities.jsonObjectToMap(instantJson); + map.put("uuid", annotation.getUuid()); WritableMap eventData = Arguments.makeNativeMap(map); rctEventEmitter.receiveEvent(getViewTag(), getEventName(), eventData); } diff --git a/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java b/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java index cf780f74..7df9cd57 100644 --- a/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java +++ b/android/src/main/java/com/pspdfkit/react/events/PdfViewDataReturnedEvent.java @@ -51,7 +51,9 @@ public PdfViewDataReturnedEvent(@IdRes int viewId, int requestId, @NonNull List< continue; } JSONObject instantJson = new JSONObject(annotation.toInstantJson()); - annotationsSerialized.add(JsonUtilities.jsonObjectToMap(instantJson)); + Map annotationMap = JsonUtilities.jsonObjectToMap(instantJson); + annotationMap.put("uuid", annotation.getUuid()); + annotationsSerialized.add(annotationMap); } Map annotations = new HashMap<>(); diff --git a/android/src/main/java/com/pspdfkit/react/helper/PSPDFKitUtils.kt b/android/src/main/java/com/pspdfkit/react/helper/PSPDFKitUtils.kt index f3d0e148..4e154839 100644 --- a/android/src/main/java/com/pspdfkit/react/helper/PSPDFKitUtils.kt +++ b/android/src/main/java/com/pspdfkit/react/helper/PSPDFKitUtils.kt @@ -17,7 +17,8 @@ class PSPDFKitUtils { ) @JvmStatic - public fun isValidImage(file: File): Boolean { + public fun isValidImage(path: String): Boolean { + val file = File(path) for (extension in SUPPORTED_IMAGE_TYPES) { if (file.name.lowercase(Locale.getDefault()).endsWith(extension)) { return true @@ -27,8 +28,9 @@ class PSPDFKitUtils { } @JvmStatic - public fun isValidPdf(file: File): Boolean { - return file.name.lowercase(Locale.getDefault()).endsWith(".pdf") + public fun isValidPdf(path: String): Boolean { + val file = File(path) + return file.name.lowercase(Locale.getDefault()).endsWith(".pdf") } @JvmStatic diff --git a/android/src/main/java/com/pspdfkit/react/helper/RemoteDocumentDownloader.kt b/android/src/main/java/com/pspdfkit/react/helper/RemoteDocumentDownloader.kt new file mode 100644 index 00000000..de4892fa --- /dev/null +++ b/android/src/main/java/com/pspdfkit/react/helper/RemoteDocumentDownloader.kt @@ -0,0 +1,104 @@ +package com.pspdfkit.react.helper + +import android.content.Context +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import com.facebook.react.bridge.ReactApplicationContext +import com.pspdfkit.document.download.DownloadJob +import com.pspdfkit.document.download.DownloadProgressFragment +import com.pspdfkit.document.download.DownloadRequest +import com.pspdfkit.document.download.source.DownloadSource +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.net.HttpURLConnection +import java.net.MalformedURLException +import java.net.URL +import java.net.URLConnection + +class RemoteDocumentDownloader(private val remoteURL: String, + private var destinationFileURL: String?, + private val overwriteExisting: Boolean, + private val context: Context, + private val fragmentManager: FragmentManager) { + + fun startDownload(callback: (File?) -> Unit) { + val source: WebDownloadSource = try { + // Try to parse the URL pointing to the PDF document. + WebDownloadSource(URL(remoteURL)) + } catch (e: MalformedURLException) { + // Error while trying to parse the PDF Download URL + return + } + + if (overwriteExisting && destinationFileURL != null) { + val delete = File(destinationFileURL) + if (delete.exists()) { + delete.delete() + } + } + + val request = DownloadRequest.Builder(context) + .source(source) + .outputFile(if (destinationFileURL == null) + File(context.getDir("documents", Context.MODE_PRIVATE), "temp.pdf") else + File(destinationFileURL)) + .overwriteExisting(overwriteExisting) + .useTemporaryOutputFile(false) + .build() + + val job = DownloadJob.startDownload(request) + job.setProgressListener(object : DownloadJob.ProgressListenerAdapter() { + override fun onComplete(output: File) { + callback(output) + } + + override fun onError(exception: Throwable) { + callback(null) + } + }) + val fragment = DownloadProgressFragment() + fragment.show(fragmentManager, "download-fragment") + fragment.job = job + } +} + +class WebDownloadSource constructor(private val documentURL: URL) : DownloadSource { + /** + * The open method needs to return an [InputStream] that will provide the complete document. + */ + @Throws(IOException::class) + override fun open(): InputStream { + val connection = documentURL.openConnection() as HttpURLConnection + connection.connect() + return connection.inputStream + } + + /** + * If the length is available it can be returned here. This is optional, and can improve the reported download progress, since it will then contain + * a percentage of download. + */ + override fun getLength(): Long { + var length = DownloadSource.UNKNOWN_DOWNLOAD_SIZE + + // We try to estimate the download size using the content length header. + var urlConnection: URLConnection? = null + try { + urlConnection = documentURL.openConnection() + val contentLength = urlConnection.contentLength + if (contentLength != -1) { + length = contentLength.toLong() + } + } catch (e: IOException) { + // Error while trying to parse the PDF Download URL + } finally { + (urlConnection as? HttpURLConnection)?.disconnect() + } + return length + } + + override fun toString(): String { + return "WebDownloadSource{documentURL=$documentURL}" + } +} \ No newline at end of file diff --git a/android/src/main/java/com/pspdfkit/react/menu/ReactGroupingRule.java b/android/src/main/java/com/pspdfkit/react/menu/ReactGroupingRule.java index fdd576fa..e3ea4ce1 100644 --- a/android/src/main/java/com/pspdfkit/react/menu/ReactGroupingRule.java +++ b/android/src/main/java/com/pspdfkit/react/menu/ReactGroupingRule.java @@ -92,7 +92,7 @@ private int getIdFromName(@NonNull String name) { case "markup": return com.pspdfkit.R.id.pspdf__annotation_creation_toolbar_group_markup; case "writing": - return com.pspdfkit.R.id.pspdf__annotation_creation_toolbar_item_writing; + return com.pspdfkit.R.id.pspdf__annotation_creation_toolbar_group_writing; case "highlight": return com.pspdfkit.R.id.pspdf__annotation_creation_toolbar_item_highlight; case "squiggly": diff --git a/android/src/main/java/com/pspdfkit/views/PdfView.java b/android/src/main/java/com/pspdfkit/views/PdfView.java index 5d9587ea..949d8022 100644 --- a/android/src/main/java/com/pspdfkit/views/PdfView.java +++ b/android/src/main/java/com/pspdfkit/views/PdfView.java @@ -36,6 +36,7 @@ import androidx.core.graphics.drawable.DrawableCompat; import androidx.fragment.app.FragmentManager; +import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.common.MapBuilder; @@ -66,6 +67,7 @@ import com.pspdfkit.internal.model.ImageDocumentImpl; import com.pspdfkit.listeners.OnVisibilityChangedListener; import com.pspdfkit.listeners.SimpleDocumentListener; +import com.pspdfkit.react.PDFDocumentModule; import com.pspdfkit.react.R; import com.pspdfkit.react.events.CustomAnnotationContextualMenuItemTappedEvent; import com.pspdfkit.react.events.PdfViewAnnotationChangedEvent; @@ -82,6 +84,7 @@ import com.pspdfkit.react.helper.ConversionHelpers; import com.pspdfkit.react.helper.DocumentJsonDataProvider; import com.pspdfkit.react.helper.MeasurementsHelper; +import com.pspdfkit.react.helper.RemoteDocumentDownloader; import com.pspdfkit.react.menu.ContextualToolbarMenuItemConfig; import com.pspdfkit.signatures.storage.DatabaseSignatureStorage; import com.pspdfkit.signatures.storage.SignatureStorage; @@ -110,6 +113,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.concurrent.atomic.AtomicBoolean; @@ -125,6 +129,7 @@ import io.reactivex.rxjava3.functions.Function; import io.reactivex.rxjava3.schedulers.Schedulers; import io.reactivex.rxjava3.subjects.BehaviorSubject; +import kotlin.Unit; /** * This view displays a {@link com.pspdfkit.ui.PdfFragment} and all associated toolbars. @@ -146,6 +151,7 @@ public class PdfView extends FrameLayout { private PdfDocument document; private String documentPath; private String documentPassword; + private ReadableMap remoteDocumentConfiguration; private int pageIndex = 0; private PdfActivityConfiguration initialConfiguration; private ReadableArray pendingToolbarItems; @@ -300,7 +306,11 @@ public void setDocumentPassword(@Nullable String documentPassword) { this.documentPassword = documentPassword; } - public void setDocument(@Nullable String documentPath) { + public void setRemoteDocumentConfiguration(@Nullable ReadableMap remoteDocumentConfig) { + this.remoteDocumentConfiguration = remoteDocumentConfig; + } + + public void setDocument(@Nullable String documentPath, ReactApplicationContext reactApplicationContext) { if (documentPath == null) { this.document = null; removeFragment(false); @@ -322,34 +332,68 @@ public void setDocument(@Nullable String documentPath) { this.documentPath = documentPath; updateState(); - File documentFile = new File(documentPath); - if (PSPDFKitUtils.isValidImage(documentFile)) { - documentOpeningDisposable = ImageDocumentLoader.openDocumentAsync(getContext(), Uri.parse(documentPath)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(imageDocument -> { - PdfView.this.document = imageDocument.getDocument(); - setupFragment(false); - }, throwable -> { - PdfView.this.document = null; - setupFragment(false); - eventDispatcher.dispatchEvent(new PdfViewDocumentLoadFailedEvent(getId(), throwable.getMessage())); - }); + if (Uri.parse(documentPath).getScheme().toLowerCase(Locale.getDefault()).contains("http")) { + String outputFilePath = this.remoteDocumentConfiguration != null && + this.remoteDocumentConfiguration.hasKey("outputFilePath") ? + this.remoteDocumentConfiguration.getString("outputFilePath") : null; + + // If no output file was specified, the temporary file location should always be overwritten + Boolean overwriteExisting = this.remoteDocumentConfiguration != null && + this.remoteDocumentConfiguration.hasKey("overwriteExisting") ? + this.remoteDocumentConfiguration.getBoolean("overwriteExisting") : (outputFilePath == null ? true : false); + + RemoteDocumentDownloader downloader = new RemoteDocumentDownloader(documentPath, outputFilePath, overwriteExisting, getContext(), fragmentManager); + downloader.startDownload(fileLocation -> { + if (fileLocation != null) { + documentOpeningDisposable = PdfDocumentLoader.openDocumentAsync(getContext(), Uri.fromFile(fileLocation), documentPassword) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(pdfDocument -> { + PdfView.this.document = pdfDocument; + reactApplicationContext.getNativeModule(PDFDocumentModule.class).setDocument(pdfDocument, this.getId()); + setupFragment(false); + }, throwable -> { + // The Android SDK will present password UI, do not emit an error. + if (!(throwable instanceof InvalidPasswordException)) { + PdfView.this.document = null; + eventDispatcher.dispatchEvent(new PdfViewDocumentLoadFailedEvent(getId(), throwable.getMessage())); + } + setupFragment(true); + }); + } + return Unit.INSTANCE; + }); } else { - documentOpeningDisposable = PdfDocumentLoader.openDocumentAsync(getContext(), Uri.parse(documentPath), documentPassword) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(pdfDocument -> { - PdfView.this.document = pdfDocument; - setupFragment(false); - }, throwable -> { - // The Android SDK will present password UI, do not emit an error. - if (!(throwable instanceof InvalidPasswordException)) { + if (PSPDFKitUtils.isValidImage(documentPath)) { + documentOpeningDisposable = ImageDocumentLoader.openDocumentAsync(getContext(), Uri.parse(documentPath)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(imageDocument -> { + PdfView.this.document = imageDocument.getDocument(); + reactApplicationContext.getNativeModule(PDFDocumentModule.class).setDocument(imageDocument.getDocument(), this.getId()); + setupFragment(false); + }, throwable -> { PdfView.this.document = null; + setupFragment(false); eventDispatcher.dispatchEvent(new PdfViewDocumentLoadFailedEvent(getId(), throwable.getMessage())); - } - setupFragment(true); - }); + }); + } else { + documentOpeningDisposable = PdfDocumentLoader.openDocumentAsync(getContext(), Uri.parse(documentPath), documentPassword) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(pdfDocument -> { + PdfView.this.document = pdfDocument; + reactApplicationContext.getNativeModule(PDFDocumentModule.class).setDocument(pdfDocument, this.getId()); + setupFragment(false); + }, throwable -> { + // The Android SDK will present password UI, do not emit an error. + if (!(throwable instanceof InvalidPasswordException)) { + PdfView.this.document = null; + eventDispatcher.dispatchEvent(new PdfViewDocumentLoadFailedEvent(getId(), throwable.getMessage())); + } + setupFragment(true); + }); + } } } @@ -484,7 +528,7 @@ private void setupFragment(boolean recreate) { if (pdfFragment == null) { if (recreate == true) { - pdfFragment = PdfUiFragmentBuilder.fromUri(getContext(), Uri.parse(documentPath)).fragmentClass(ReactPdfUiFragment.class).build(); + pdfFragment = PdfUiFragmentBuilder.fromUri(getContext(), Uri.parse(this.documentPath)).fragmentClass(ReactPdfUiFragment.class).build(); } else if (document != null) { pdfFragment = PdfUiFragmentBuilder.fromDocumentDescriptor(getContext(), DocumentDescriptor.fromDocument(document)) .configuration(configuration) @@ -832,7 +876,8 @@ public Disposable setAnnotationFlags(final int requestId, String uuid, ReadableA List allAnnotations = currentDocument.getAnnotationProvider().getAllAnnotationsOfType(AnnotationProvider.ALL_ANNOTATION_TYPES); for (int i = 0; i < allAnnotations.size(); i++) { Annotation annotation = allAnnotations.get(i); - if (annotation.getUuid().equals(uuid)) { + if (annotation.getUuid().equals(uuid) || + (annotation.getName() != null && annotation.getName().equals(uuid))) { EnumSet convertedFlags = ConversionHelpers.getAnnotationFlags(flags); annotation.setFlags(convertedFlags); getCurrentPdfFragment().subscribe(pdfFragment -> { @@ -855,7 +900,8 @@ public Disposable getAnnotationFlags(final int requestId, @NonNull String uuid) ArrayList convertedFlags = new ArrayList<>(); for (int i = 0; i < allAnnotations.size(); i++) { Annotation annotation = allAnnotations.get(i); - if (annotation.getUuid().equals(uuid)) { + if (annotation.getUuid().equals(uuid) || + (annotation.getName() != null && annotation.getName().equals(uuid))) { EnumSet flags = annotation.getFlags(); convertedFlags = ConversionHelpers.convertAnnotationFlags(flags); break; diff --git a/documentation/configuration-options.md b/documentation/configuration-options.md index bce9501d..c702e18e 100644 --- a/documentation/configuration-options.md +++ b/documentation/configuration-options.md @@ -1,116 +1,5 @@ ### Configuration Options -Here's the complete list of configuration options supported by each platform. Note that some options are only supported on a single platform — that's because of differences in the behavior of both of these platforms. Options that work on only one platform are prefixed with the appropriate platform name: `android` or `iOS`. The options, grouped roughly by category, are shown below. +Here's the complete list of configuration options supported by each platform. Note that some options are only supported on a single platform — that's because of differences in the behavior of both of these platforms. Options that work on only one platform are prefixed with the appropriate platform name: `android` or `iOS`. -#### Document Interaction Options - -| Configuration Option | Data Type | Possible Values | iOS | Android | Documentation | -| ---------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `scrollDirection` | `String` | `horizontal`, `vertical` | ✅ | ✅ | Configures the direction of page scrolling in the document view. | -| `pageTransition` | `String` | `scrollPerSpread`, `scrollContinuous`, `curl` | ✅ | ✅ | Configures the page scrolling mode. Note that curl mode is only available for iOS and will be ignored on Android. | -| `documentPassword` | `String` | | ✅ | ✅ | The password to unlock the document. | -| `enableTextSelection` | `Boolean` | `true` / `false` | ✅ | ✅ | Allow / disallow text selection. | -| `autosaveEnabled` | `Boolean` | `true` / `false` | ✅ | ✅ | Determines whether PSPDFKit should save automatically in response to [certain UI triggers][], such as the app entering the background or the view disappearing. | -| `disableAutomaticSaving` | `Boolean` | `true` / `false` | ✅ | ✅ | Determines whether PSPDFKit should save automatically in response to [certain UI triggers][], such as the app entering the background or the view disappearing. | -| `signatureSavingStrategy` | `String` | `alwaysSave`, `neverSave`, `saveIfSelected` | ✅ | ✅ | Determines whether signatures should be saved after creation. | -| `iOSShouldScrollToChangedPage` | `Boolean` | `true` / `false` | ✅ | ❌ | Scrolls to the affected page during an undo / redo operation. | -| `iOSScrollViewInsetAdjustment` | `String` | `none`, `fixedElements`, `allElements` | ✅ | ❌ | Sets the scroll view inset adjustment mode. | -| `iOSFormElementZoomEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Option to automatically focus on selected form elements. | -| `iOSImageSelectionEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Allow / disallow image selection. | -| `iOSTextSelectionShouldSnapToWord` | `Boolean` | `true` / `false` | ✅ | ❌ | Configure if text selection should snap to words. | -| `iOSFreeTextAccessoryViewEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Shows a toolbar with text editing options above the keyboard while editing free text annotations. | -| `iOSInternalTapGesturesEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Enable / disable all internal gesture recognizers. | -| `iOSAllowBackgroundSaving` | `Boolean` | `true` / `false` | ✅ | ❌ | Determines whether automatic saving should happen on a background thread. | -| `iOSMinimumZoomScale` | `float` | - | ✅ | ❌ | Minimum zoom scale for the scroll view. | -| `iOSMaximumZoomScale` | `float` | - | ✅ | ❌ | Maximum zoom scale for the scroll view. | -| `iOSDoubleTapAction` | `String` | `none`, `zoom`, `smartZoom` | ✅ | ❌ | The action that happens when the user double taps somewhere in the document. | -| `iOSTextSelectionMode` | `String` | `regular`, `simple`, `automatic` | ✅ | ❌ | Defines how the text is selected. | -| `iOSTypesShowingColorPresets` | `Set` | `none`, `undefined`, `all`, `Link`, `Highlight`, `Underline`, `Squiggly`, `StrikeOut`, `Text`, `Caret`, `FreeText`, `Ink`, `Square`, `Circle`, `Line`, `Signature`, `Stamp`, `Eraser`, `Image`, `Widget`, `FileAttachment`, `Sound`, `Polygon`, `PolyLine`, `RichMedia`, `Screen`, `Popup`, `Watermark`, `TrapNet`, `3D`, `Redact` | ✅ | ❌ | Shows a custom cell with configurable color presets for the provided annotation types. | - -#### Document Presentation Options - -| Configuration Option | Data Type | Possible Values | iOS | Android | Documentation | -| ----------------------------- | --------- | ---------------------------------- | --- | ------- | ----------------------------------------------------------------------------------------------- | -| `pageMode` | `String` | `single`, `double`, `automatic` | ✅ | ✅ | Configure the page mode. | -| `firstPageAlwaysSingle` | `Boolean` | `true` / `false` | ✅ | ✅ | Option to show the first page separately. | -| `showPageLabels` | `Boolean` | `true` / `false` | ✅ | ✅ | Displays the current page number. | -| `documentLabelEnabled` | `Bool` | `true` / `false` | ✅ | ✅ | Shows an overlay displaying the document name. | -| `spreadFitting` | `String` | `fit`, `fill`, `adaptive` | ✅ | ✅ | Controls the page fitting mode. `adaptive` mode only works on iOS and has no effect on Android. | -| `invertColors` | `Boolean` | `true` / `false` | ✅ | ✅ | Inverts the document color if `true`. | -| `androidGrayScale` | `Boolean` | `true` / `false` | ❌ | ✅ | Converts the document colors to grayscale. | -| `iOSClipToPageBoundaries` | `Boolean` | `true` / `false` | ✅ | ❌ | Option to clip content to page boundaries. | -| `iOSBackgroundColor` | `UIColor` | - | ✅ | ❌ | Background color behind the page view. | -| `iOSRenderAnimationEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Shows a `UIActivityIndicatorView` in the top-right corner while the page is rendering. | -| `iOSRenderStatusViewPosition` | `String` | `top`, `centered` | ✅ | ❌ | Position of the render status view. | -| `iOSAllowedAppearanceModes` | `String` | `default`, `sepia`, `night`, `all` | ✅ | ❌ | Allowed appearance modes for `BrightnessViewController`. | - -#### User Interface Options - -| Configuration Option | Data Type | Possible Values | iOS | Android | Documentation | -| --------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------- | --- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `userInterfaceViewMode` | `String` | `automatic`, `automaticBorderPages`, `automaticNoFirstLastPage`, `always`, `alwaysVisible`, `alwaysHidden`, `never` | ✅ | ✅ | Configures the user interface visibility. | -| `inlineSearch` | `Boolean` | `true` / `false` | ✅ | ✅ | Sets the type of search bar to be inline or modular. | -| `immersiveMode` | `Boolean` | `true` / `false` | ✅ | ✅ | Hides the user interface if set to `true`. | -| `toolbarTitle` | `String` | - | ✅ | ✅ | Sets the title of the toolbar. Note: For iOS, you need to set `documentLabelEnabled`, `iOSUseParentNavigationBar`, and `iOSAllowToolbarTitleChange` to `false` in your configuration before setting the custom title. | -| `androidShowSearchAction` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables / disables document search functionality. | -| `androidShowOutlineAction` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables an outline menu in the activity. | -| `androidShowBookmarksAction` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables the display of bookmarks. | -| `androidShowShareAction` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables the display of share features. | -| `androidShowPrintAction` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables the printing option in the menu (if applicable) for the document and the device. | -| `androidShowDocumentInfoView` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables the display of document information. | -| `androidShowSettingsMenu` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables the display of the settings menu. | -| `iOSShouldHideUserInterfaceOnPageChange` | `Boolean` | `true` / `false` | ✅ | ❌ | Option to hide / show the user interface when changing pages. | -| `iOSShouldShowUserInterfaceOnViewWillAppear` | `Boolean` | `true` / `false` | ✅ | ❌ | Option to hide / show the user interface when the page appears. | -| `iOSShouldHideStatusBarWithUserInterface` | `Boolean` | `true` / `false` | ✅ | ❌ | Option to hide / show the status bar with the user interface. | -| `iOSShouldHideNavigationBarWithUserInterface` | `Boolean` | `true` / `false` | ✅ | ❌ | Option to hide / show the navigation bar with the user interface. | -| `iOSSearchMode` | `String` | `modal`, `inline` | ✅ | ❌ | Sets the type of search bar to be inline or modal. | -| `iOSScrollOnEdgeTapEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Determines whether tapping on leading / trailing edges of the document view should trigger changing to the previous / next page. | -| `iOSScrollOnEdgeTapMargin` | `float` | - | ✅ | ❌ | The margin in points from the view’s sides in which tapping should trigger scrolling to the previous / next page. | -| `iOSUseParentNavigationBar` | `Boolean` | `true` / `false` | ✅ | ❌ | Set this to `true` to allow this controller to access the parent `navigationBar` / `navigationController` to add custom buttons. | -| `iOSAllowToolbarTitleChange` | `Boolean` | `true` / `false` | ✅ | ❌ | Allow PSPDFKit to change the title of this view controller. | -| `iOSShouldHideStatusBar` | `Boolean` | `true` / `false` | ✅ | ❌ | If `true`, the status bar will always remain hidden (regardless of the `shouldHideStatusBarWithUserInterface` setting). | -| `iOSShowBackActionButton` | `Boolean` | `true` / `false` | ✅ | ❌ | Shows a floating back button in the lower part of the screen. | -| `iOSShowForwardActionButton` | `Boolean` | `true` / `false` | ✅ | ❌ | Shows a floating forward button in the lower part of the screen. | -| `iOSShowBackForwardActionButtonLabels` | `Boolean` | `true`/ `false` | ✅ | ❌ | Adds text labels representing the destination name to the back and forward buttons. | -| `iOSSearchResultZoomScale` | `float` | - | ✅ | ❌ | Increase this to zoom to the search result. | -| `iOSAdditionalScrollViewFrameInsets` | `UIEdgeInsets` | - | ✅ | ❌ | Additional insets to apply to the document scroll view's frame. | -| `iOSAdditionalContentInsets` | `UIEdgeInsets` | - | ✅ | ❌ | Additional insets to apply to the layout's content. | -| `iOSAllowedMenuActions` | `String` | `none`, `search`, `define`, `wikipedia`, `speak`, `all` | ✅ | ❌ | May be used to customize other displayed menu actions when text is selected. | -| `iOSSettingsOptions` | `Set` | `scrollDirection`, `pageTransition`, `appearance`, `brightness`, `pageMode`, `spreadFitting`, `default`, `all` | ✅ | ❌ | Options that will be presented by `PDFSettingsViewController`. Defaults to `.default`. | -| `iOSShadowEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Enable / disable page shadow. | -| `iOSShadowOpacity` | `float` | - | ✅ | ❌ | Set the default `shadowOpacity`. | -| `measurementValueConfigurations` | `MeasurementValueConfiguration`[] | - | ✅ | ✅ | The array of `MeasurementValueConfiguration` objects that should be applied to the document. | - -#### Thumbnail Options - -| Configuration Option | Data Type | Possible Values | iOS | Android | Documentation | -| -------------------------------- | -------------- | -------------------------------------------------------------------- | --- | ------- | ------------------------------------------------------------------------------ | -| `showThumbnailBar` | `String` | `none`, `default`, `floating`, `pinned`, `scrubberBar`, `scrollable` | ✅ | ✅ | Thumbnail bar mode controls the display of page thumbnails viewing a document. | -| `androidShowThumbnailGridAction` | `Boolean` | `true` / `false` | ❌ | ✅ | Displays an action bar icon to show a grid of thumbnail pages. | -| `iOSScrubberBarType` | `String` | `horizontal`, `verticalLeft`, `verticalRight` | ✅ | ❌ | Controls the placement of the scrubber bar. | -| `iOSThumbnailGrouping` | `String` | `automatic`, `never`, `always` | ✅ | ❌ | Option to set the grouping of thumbnails. | -| `iOSThumbnailSize` | `CGSize` | - | ✅ | ❌ | Configure the size of the thumbnail. | -| `iOSThumbnailInteritemSpacing` | `float` | - | ✅ | ❌ | Configure the spacing between thumbnails. | -| `iOSThumbnailLineSpacing` | `float` | - | ✅ | ❌ | Configure the line spacing of thumbnails. | -| `iOSThumbnailMargin` | `UIEdgeInsets` | - | ✅ | ❌ | Configure the margin for thumbnails. | -| `iOSShouldCacheThumbnails` | `Boolean` | `true` / `false` | ✅ | ❌ | Option to enable / disable thumbnail caching. | - -#### Annotation, Forms, and Bookmark Options - -| Configuration Option | Data Type | Possible Values | iOS | Android | Documentation | -| -------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `editableAnnotationTypes` | `Set` | `none`, `undefined`, `all`, `Link`, `Highlight`, `Underline`, `Squiggly`, `StrikeOut`, `Text`, `Caret`, `FreeText`, `Ink`, `Square`, `Circle`, `Line`, `Signature`, `Stamp`, `Eraser`, `Image`, `Widget`, `FileAttachment`, `Sound`, `Polygon`, `PolyLine`, `RichMedia`, `Screen`, `Popup`, `Watermark`, `TrapNet`, `3D`, `Redact` | ✅ | ✅ | Set containing the annotation types that should be editable. | -| `enableAnnotationEditing` | `Boolean` | `true` / `false` | ✅ | ✅ | Configuration to enable / disable editing all annotations. To selectively enable editing for specific types of annotations, use `editableAnnotationTypes`. | -| `enableFormEditing` | `Boolean` | `true` / `false` | ✅ | ✅ | Configuration to enable / disable editing forms. This can also be accomplished by adding / removing the `Widget` annotation type from `editableAnnotationTypes`. | -| `androidShowAnnotationListAction` | `Boolean` | `true` / `false` | ❌ | ✅ | Enables the list of annotations. | -| `iOSShouldAskForAnnotationUsername` | `Boolean` | `true` / `false` | ✅ | ❌ | If `true`, asks the user to specify a custom annotation user name ("author") when creating a new annotation. | -| `iOSLinkAction` | `String` | `none`, `alertView`, `openSafari`, `inlineBrowser`, `InlineWebViewController` | ✅ | ❌ | Sets the default link action for pressing on `LinkAnnotation`s. | -| `iOSDrawCreateMode` | `String` | `separate`, `mergeIfPossible` | ✅ | ❌ | Determines whether new annotations are created when strokes end. | -| `iOSAnnotationGroupingEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | If set to `true`, you can group / ungroup annotations with the multi-select tool. | -| `iOSNaturalDrawingAnnotationEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Enables natural drawing for ink annotations. | -| `iOSNaturalSignatureDrawingEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Enables natural drawing for signatures. | -| `iOSAnnotationEntersEditModeAfterSecondTapEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | Controls if a second tap to an annotation that allows inline editing enters edit mode. | -| `iOSCreateAnnotationMenuEnabled` | `Boolean` | `true` / `false` | ✅ | ❌ | If set to `true`, a long tap that ends on a page area that isn’t a text / image will show a new menu to create annotations. | -| `iOSAnnotationAnimationDuration` | `float` | - | ✅ | ❌ | Overlay annotations are faded in. Set the global duration for this fade here. | -| `iOSSoundAnnotationTimeLimit` | `float` | - | ✅ | ❌ | Describes the time limit for recording sound annotations, in seconds. | -| `iOSBookmarkSortOrder` | `String` | `custom`, `pageBased` | ✅ | ❌ | Controls how bookmarks are displayed and managed. | +This list has now moved to the React Native API Reference. See the available configuration options here: https://pspdfkit.com/api/react-native/PDFConfiguration.html \ No newline at end of file diff --git a/index.js b/index.js index 1cd95f05..aef1c475 100644 --- a/index.js +++ b/index.js @@ -42,6 +42,10 @@ class PSPDFKitView extends React.Component { * @ignore */ _requestMap = new Map(); + /** + * @ignore + */ + _pdfDocument = null; render() { if (Platform.OS === 'ios' || Platform.OS === 'android') { @@ -256,6 +260,19 @@ class PSPDFKitView extends React.Component { } }; + /** + * Get the current PDF document. + * @method getDocument + * @example + * const document = this.pdfRef.current?.getDocument(); + * @see {@link https://pspdfkit.com/api/react-native/PDFDocument.html} for available methods. + * @memberof PSPDFKitView + * @returns { PDFDocument } A reference to the document that is currently loaded in the PSPDFKitView component. + */ + getDocument = function () { + return this._pdfDocument == null ? new PDFDocument(this.refs.pdfView) : this._pdfDocument; + }; + /** * Gets all annotations of the given type from the specified page. * @@ -1714,7 +1731,7 @@ export class PSPDFKit { */ /** - * @typedef PDFDocument + * @typedef PDFDocumentConfiguration * @property { string } documentPath The URI to the existing document. * @property { number } pageIndex The index of the page that should be used from the document. Starts at 0. */ @@ -1723,7 +1740,7 @@ export class PSPDFKit { * @typedef DocumentPDFConfiguration * @property { string } [name] The name of the new document. * @property { string } [filePath] The directory where the new document should be stored. - * @property { Array } documents An array of the documents that should be used to construct the new document. + * @property { Array } documents An array of the documents that should be used to construct the new document. * @property { boolean } override If ```true```, will override existing document with the same name. */ @@ -1914,6 +1931,9 @@ export class Processor { import { PDFConfiguration } from "./lib/configuration/PDFConfiguration"; export { PDFConfiguration } from "./lib/configuration/PDFConfiguration"; +import { RemoteDocumentConfiguration } from "./lib/configuration/PDFConfiguration"; +export { RemoteDocumentConfiguration } from "./lib/configuration/PDFConfiguration"; + import { Toolbar } from "./lib/toolbar/Toolbar"; export { Toolbar } from "./lib/toolbar/Toolbar"; @@ -1935,11 +1955,16 @@ export { AnnotationContextualMenu } from "./lib/annotations/Annotation"; import { AnnotationContextualMenuItem } from "./lib/annotations/Annotation"; export { AnnotationContextualMenuItem } from "./lib/annotations/Annotation"; +import { PDFDocument } from "./lib/document/PDFDocument"; +export { PDFDocument } from "./lib/document/PDFDocument"; + module.exports.PDFConfiguration = PDFConfiguration; +module.exports.RemoteDocumentConfiguration = RemoteDocumentConfiguration; module.exports.Toolbar = Toolbar; module.exports.Measurements = Measurements; module.exports.MeasurementScale = MeasurementScale; module.exports.MeasurementValueConfiguration = MeasurementValueConfiguration; module.exports.Annotation = Annotation; module.exports.AnnotationContextualMenu = AnnotationContextualMenu; -module.exports.AnnotationContextualMenuItem = AnnotationContextualMenuItem; \ No newline at end of file +module.exports.AnnotationContextualMenuItem = AnnotationContextualMenuItem; +module.exports.PDFDocument = PDFDocument; \ No newline at end of file diff --git a/ios/RCTPSPDFKit.xcodeproj/project.pbxproj b/ios/RCTPSPDFKit.xcodeproj/project.pbxproj index 2d048a3f..3546ed53 100644 --- a/ios/RCTPSPDFKit.xcodeproj/project.pbxproj +++ b/ios/RCTPSPDFKit.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 0756A13E29C2124C00D3462C /* NSExceptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0756A13A29C2124B00D3462C /* NSExceptionError.swift */; }; 0756A13F29C2124C00D3462C /* InstantDocumentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0756A13B29C2124B00D3462C /* InstantDocumentViewController.swift */; }; 07D79CA52A086568006E90D7 /* PspdfkitMeasurementConvertor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07D79CA42A086568006E90D7 /* PspdfkitMeasurementConvertor.swift */; }; + 4856D2FD2BEC037E00EA03C6 /* PDFDocumentManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 4856D2FB2BEC037E00EA03C6 /* PDFDocumentManager.m */; }; + 4856D2FE2BEC037E00EA03C6 /* PDFDocumentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4856D2FC2BEC037E00EA03C6 /* PDFDocumentManager.swift */; }; 48A73FC42BD8281500BDD8FD /* AnnotationConfigurationsConvertor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48A73FC32BD8281500BDD8FD /* AnnotationConfigurationsConvertor.swift */; }; 48A73FC62BD8283100BDD8FD /* RCTConvert+PSPDFAnnotationFlags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48A73FC52BD8283100BDD8FD /* RCTConvert+PSPDFAnnotationFlags.swift */; }; 48A73FC82BD8283A00BDD8FD /* RCTConvert+PSPDFEditMenuAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48A73FC72BD8283A00BDD8FD /* RCTConvert+PSPDFEditMenuAppearance.swift */; }; @@ -56,6 +58,8 @@ 0756A13A29C2124B00D3462C /* NSExceptionError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSExceptionError.swift; sourceTree = ""; }; 0756A13B29C2124B00D3462C /* InstantDocumentViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstantDocumentViewController.swift; sourceTree = ""; }; 07D79CA42A086568006E90D7 /* PspdfkitMeasurementConvertor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PspdfkitMeasurementConvertor.swift; sourceTree = ""; }; + 4856D2FB2BEC037E00EA03C6 /* PDFDocumentManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PDFDocumentManager.m; sourceTree = ""; }; + 4856D2FC2BEC037E00EA03C6 /* PDFDocumentManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PDFDocumentManager.swift; sourceTree = ""; }; 48A73FC32BD8281500BDD8FD /* AnnotationConfigurationsConvertor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnnotationConfigurationsConvertor.swift; sourceTree = ""; }; 48A73FC52BD8283100BDD8FD /* RCTConvert+PSPDFAnnotationFlags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RCTConvert+PSPDFAnnotationFlags.swift"; sourceTree = ""; }; 48A73FC72BD8283A00BDD8FD /* RCTConvert+PSPDFEditMenuAppearance.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "RCTConvert+PSPDFEditMenuAppearance.swift"; sourceTree = ""; }; @@ -111,6 +115,8 @@ 0756A13B29C2124B00D3462C /* InstantDocumentViewController.swift */, 0756A13A29C2124B00D3462C /* NSExceptionError.swift */, 0756A13829C2124B00D3462C /* UIColor.swift */, + 4856D2FB2BEC037E00EA03C6 /* PDFDocumentManager.m */, + 4856D2FC2BEC037E00EA03C6 /* PDFDocumentManager.swift */, 6540D1831D89D22E00B8F94F /* RCTPSPDFKitManager.m */, F8C1A2E6202DCD3800E98192 /* RCTPSPDFKitViewManager.h */, F8C1A2E4202DCC9700E98192 /* RCTPSPDFKitViewManager.m */, @@ -251,8 +257,10 @@ B783BA3421C3F55300FD981A /* RCTConvert+PSPDFAnnotationToolbarConfiguration.m in Sources */, F84F8B192032D54F00153D9E /* RCTPSPDFKitView.m in Sources */, 6572780C1D86AE7300A5E1A8 /* RCTConvert+PSPDFConfiguration.m in Sources */, + 4856D2FE2BEC037E00EA03C6 /* PDFDocumentManager.swift in Sources */, 48A73FC62BD8283100BDD8FD /* RCTConvert+PSPDFAnnotationFlags.swift in Sources */, 48A73FC42BD8281500BDD8FD /* AnnotationConfigurationsConvertor.swift in Sources */, + 4856D2FD2BEC037E00EA03C6 /* PDFDocumentManager.m in Sources */, 84BC2EAD229EE9FF00A386C6 /* RCTConvert+PSPDFViewMode.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.h b/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.h index 8b686952..5d6acebf 100644 --- a/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.h +++ b/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.h @@ -14,6 +14,7 @@ @interface RCTConvert (PSPDFDocument) + (PSPDFDocument *)PSPDFDocument:(NSString *)string; ++ (PSPDFDocument *)PSPDFDocument:(NSString *)urlString remoteDocumentConfig:(NSDictionary *)remoteDocumentConfig; + (NSURL*)parseURL:(NSString*)urlString; @end diff --git a/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.m b/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.m index 2286a4a3..4b86eb2c 100644 --- a/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.m +++ b/ios/RCTPSPDFKit/Converters/RCTConvert+PSPDFDocument.m @@ -8,9 +8,33 @@ // #import "RCTConvert+PSPDFDocument.h" +#import "RCTPSPDFKitView.h" +#if __has_include("PSPDFKitReactNativeiOS-Swift.h") +#import "PSPDFKitReactNativeiOS-Swift.h" +#else +#import +#endif @implementation RCTConvert (PSPDFDocument) ++ (PSPDFDocument *)PSPDFDocument:(NSString *)urlString remoteDocumentConfig:(NSDictionary *)remoteDocumentConfig { + NSURL* url = [RCTConvert parseURL:urlString]; + + PSPDFDocument *document = nil; + NSString *outputFilePath = remoteDocumentConfig[@"outputFilePath"]; + BOOL overwriteExisting = [remoteDocumentConfig[@"overwriteExisting"] boolValue]; + + if ([[url scheme] containsString:@"http"] && outputFilePath != nil) { + NSURL *destination = [RCTConvert parseURL:outputFilePath]; + RemoteDocumentDownloader *downloader = [[RemoteDocumentDownloader alloc] initWithRemoteURL:url destinationFileURL:destination cleanup:overwriteExisting]; + PSPDFCoordinatedFileDataProvider *provider = [[PSPDFCoordinatedFileDataProvider alloc] initWithFileURL:destination progress:downloader.progress]; + document = [[PSPDFDocument alloc] initWithDataProviders:@[provider]]; + } else { + document = [RCTConvert PSPDFDocument:urlString]; + } + return document; +} + + (PSPDFDocument *)PSPDFDocument: (NSString *)urlString { NSURL* url = [self parseURL: urlString]; @@ -44,6 +68,10 @@ + (NSURL*)parseURL:(NSString*)urlString { if (url == nil && [urlString containsString: @".pdf"]) { url = [[NSBundle mainBundle] URLForResource: urlString withExtension: @"pdf"]; } + + if (url == nil && [urlString containsString:@"http"]) { + url = [NSURL URLWithString:urlString]; + } return url; } diff --git a/ios/RCTPSPDFKit/Helpers/RemoteDocumentDownloader.swift b/ios/RCTPSPDFKit/Helpers/RemoteDocumentDownloader.swift new file mode 100644 index 00000000..f108597e --- /dev/null +++ b/ios/RCTPSPDFKit/Helpers/RemoteDocumentDownloader.swift @@ -0,0 +1,73 @@ +// +// Copyright © 2018-2024 PSPDFKit GmbH. All rights reserved. +// +// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +// This notice may not be removed from this file. +// + +import Foundation + +@objc public class RemoteDocumentDownloader: NSObject, URLSessionDelegate, URLSessionDownloadDelegate { + + @objc public let progress = Progress(totalUnitCount: 100) + let downloadProgress = Progress(totalUnitCount: 1) + let moveProgress = Progress(totalUnitCount: 1) + + let destinationFileURL: URL + let remoteURL: URL + + var session: URLSession? + var task: URLSessionDownloadTask? + + @objc public init(remoteURL: URL, destinationFileURL: URL, cleanup: Bool) { + self.remoteURL = remoteURL + self.destinationFileURL = destinationFileURL + + super.init() + + if (cleanup) { + self.cleanup() + } + + // Download the file using URLSession API. + let session = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: OperationQueue.main) + self.session = session + let task = session.downloadTask(with: remoteURL) + self.task = task + task.resume() + + progress.addChild(downloadProgress, withPendingUnitCount: 99) + progress.addChild(moveProgress, withPendingUnitCount: 1) + } + + func cleanup() { + do { + task?.cancel() + session?.invalidateAndCancel() + try FileManager().removeItem(at: destinationFileURL) + } catch { + print(error) + } + } + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { + try? FileManager().moveItem(at: location, to: destinationFileURL) + // We must ensure that the complete progress (`progress`) only completes when the file is already at its final location. + moveProgress.completedUnitCount = moveProgress.totalUnitCount + } + + public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { + downloadProgress.totalUnitCount = totalBytesExpectedToWrite + downloadProgress.completedUnitCount = totalBytesWritten + } + + public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { + if let error { + print(error.localizedDescription) + // Complete the progress. + progress.completedUnitCount = progress.totalUnitCount + } + } +} diff --git a/ios/RCTPSPDFKit/InstantDocumentViewController.swift b/ios/RCTPSPDFKit/InstantDocumentViewController.swift index bfeaffb8..9daba2e7 100644 --- a/ios/RCTPSPDFKit/InstantDocumentViewController.swift +++ b/ios/RCTPSPDFKit/InstantDocumentViewController.swift @@ -20,7 +20,7 @@ public class InstantDocumentViewController: InstantViewController { @objc var manager: RCTPSPDFKitManager? - @objc public init(documentInfo: InstantDocumentInfo, configurations: PDFConfiguration) throws { + @objc public init(documentInfo: InstantDocumentInfo, configuration: PDFConfiguration) throws { /* Create the Instant objects with the information from the PSPDFKit for Web examples server. @@ -47,7 +47,7 @@ public class InstantDocumentViewController: InstantViewController { let pdfDocument = documentDescriptor.editableDocument // Set the document on the `PSPDFInstantViewController` (the superclass) so it can show the download progress, and then show the document. - super.init(document: pdfDocument,configuration: configurations) + super.init(document: pdfDocument, configuration:configuration) } @available(*, unavailable) diff --git a/ios/RCTPSPDFKit/PDFDocumentManager.m b/ios/RCTPSPDFKit/PDFDocumentManager.m new file mode 100644 index 00000000..95f8c88d --- /dev/null +++ b/ios/RCTPSPDFKit/PDFDocumentManager.m @@ -0,0 +1,20 @@ +// +// Copyright © 2018-2024 PSPDFKit GmbH. All rights reserved. +// +// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +// This notice may not be removed from this file. +// + +#import +#import "React/RCTBridgeModule.h" +#import "React/RCTEventEmitter.h" +#import + +@interface RCT_EXTERN_MODULE(PDFDocumentManager, NSObject) + +RCT_EXTERN_METHOD(getDocumentId:(NSNumber _Nonnull)reference onSuccess:(RCTPromiseResolveBlock)resolve onError:(RCTPromiseRejectBlock)reject); +RCT_EXTERN_METHOD(invalidateCache:(NSNumber _Nonnull)reference onSuccess:(RCTPromiseResolveBlock)resolve onError:(RCTPromiseRejectBlock)reject); +RCT_EXTERN_METHOD(invalidateCacheForPage:(NSNumber _Nonnull)reference pageIndex:(NSInteger)pageIndex onSuccess:(RCTPromiseResolveBlock)resolve onError:(RCTPromiseRejectBlock)reject); +@end diff --git a/ios/RCTPSPDFKit/PDFDocumentManager.swift b/ios/RCTPSPDFKit/PDFDocumentManager.swift new file mode 100644 index 00000000..ff8e8a17 --- /dev/null +++ b/ios/RCTPSPDFKit/PDFDocumentManager.swift @@ -0,0 +1,55 @@ +// +// Copyright © 2018-2024 PSPDFKit GmbH. All rights reserved. +// +// THIS SOURCE CODE AND ANY ACCOMPANYING DOCUMENTATION ARE PROTECTED BY INTERNATIONAL COPYRIGHT LAW +// AND MAY NOT BE RESOLD OR REDISTRIBUTED. USAGE IS BOUND TO THE PSPDFKIT LICENSE AGREEMENT. +// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES. +// This notice may not be removed from this file. +// + +import Foundation +import React +import PSPDFKit + +@objc(PDFDocumentManager) public class PDFDocumentManager: NSObject { + + var documents = [NSNumber:Document]() + + private func getDocument(_ reference: NSNumber) -> Document? { + return documents[reference] + } + + @objc public func setDocument(_ document: Document, reference: NSNumber) { + self.documents[reference] = document + } + + @objc static public func requiresMainQueueSetup() -> Bool { + return true + } + + @objc func getDocumentId(_ reference: NSNumber, onSuccess: @escaping RCTPromiseResolveBlock, onError: @escaping RCTPromiseRejectBlock) -> Void { + guard let document = getDocument(reference) else { + onError("getDocumentId", "Document is nil", nil) + return + } + onSuccess(document.documentIdString); + } + + @objc func invalidateCacheForPage(_ reference: NSNumber, pageIndex: Int, onSuccess: @escaping RCTPromiseResolveBlock, onError: @escaping RCTPromiseRejectBlock) -> Void { + guard let document = getDocument(reference) else { + onError("invalidateCacheForPage", "Document is nil", nil) + return + } + SDK.shared.cache.invalidateImages(from: document, pageIndex: PageIndex(pageIndex)) + onSuccess(true) + } + + @objc func invalidateCache(_ reference: NSNumber, onSuccess: @escaping RCTPromiseResolveBlock, onError: @escaping RCTPromiseRejectBlock) -> Void { + guard let document = getDocument(reference) else { + onError("invalidateCache", "Document is nil", nil) + return + } + SDK.shared.cache.remove(for: document) + onSuccess(true) + } +} diff --git a/ios/RCTPSPDFKit/RCTPSPDFKitManager.m b/ios/RCTPSPDFKit/RCTPSPDFKitManager.m index a7cabcc4..9ccd2a56 100644 --- a/ios/RCTPSPDFKit/RCTPSPDFKitManager.m +++ b/ios/RCTPSPDFKit/RCTPSPDFKitManager.m @@ -129,17 +129,17 @@ @implementation RCTPSPDFKitManager } NSNumber* delay = [NSNumber numberWithInt:[configuration[@"delay"] intValue]]; [parsedConfig removeObjectForKey:@"delay"]; + + PSPDFConfiguration* pdfConfiguration = [[PSPDFConfiguration defaultConfiguration] configurationUpdatedWithBuilder:^(PSPDFConfigurationBuilder * _Nonnull builder) { + if([[configuration objectForKey:@"enableInstantComments"] isEqual:@(YES)]) { + NSMutableSet *editableAnnotationTypes = [builder.editableAnnotationTypes mutableCopy]; + [editableAnnotationTypes addObject:PSPDFAnnotationStringInstantCommentMarker]; + builder.editableAnnotationTypes = editableAnnotationTypes; + } + [builder setupFromJSON:parsedConfig]; + }]; - PSPDFConfiguration* pdfConfiguration = [[PSPDFConfiguration alloc] initWithDictionary: parsedConfig error:&error]; - - InstantDocumentViewController *instantViewController = [[InstantDocumentViewController alloc] initWithDocumentInfo:documentInfo configurations: [pdfConfiguration configurationUpdatedWithBuilder:^(PSPDFConfigurationBuilder * builder) { - // Add `PSPDFAnnotationStringInstantCommentMarker` to the `editableAnnotationTypes` to enable editing of Instant Comments. - if([[configuration objectForKey:@"enableInstantComments"] isEqual: @(YES)]) { - NSMutableSet *editableAnnotationTypes = [builder.editableAnnotationTypes mutableCopy]; - [editableAnnotationTypes addObject:PSPDFAnnotationStringInstantCommentMarker]; - builder.editableAnnotationTypes = editableAnnotationTypes; - } - }] error:nil]; + InstantDocumentViewController *instantViewController = [[InstantDocumentViewController alloc] initWithDocumentInfo:documentInfo configuration:pdfConfiguration error:&error]; if ([delay doubleValue] > 0) { instantViewController.documentDescriptor.delayForSyncingLocalChanges = [delay doubleValue]; diff --git a/ios/RCTPSPDFKit/RCTPSPDFKitView.m b/ios/RCTPSPDFKit/RCTPSPDFKitView.m index 03b58daa..223a824d 100644 --- a/ios/RCTPSPDFKit/RCTPSPDFKitView.m +++ b/ios/RCTPSPDFKit/RCTPSPDFKitView.m @@ -140,7 +140,10 @@ - (BOOL)enterAnnotationCreationMode { } - (BOOL)exitCurrentlyActiveMode { - return [self.pdfController.annotationToolbarController hideToolbarAnimated:YES completion:NULL]; + if ([self.pdfController.annotationToolbarController isToolbarVisible]) { + return [self.pdfController.annotationToolbarController hideToolbarAnimated:YES completion:NULL]; + } + return true; } - (BOOL)saveCurrentDocumentWithError:(NSError *_Nullable *)error { @@ -168,7 +171,9 @@ - (BOOL)pdfViewController:(PSPDFViewController *)pdfController didTapOnAnnotatio NSData *annotationData = [annotation generateInstantJSONWithError:NULL]; if (annotationData != nil) { NSDictionary *annotationDictionary = [NSJSONSerialization JSONObjectWithData:annotationData options:kNilOptions error:NULL]; - self.onAnnotationTapped(annotationDictionary); + NSMutableDictionary *updatedDictionary = [[NSMutableDictionary alloc] initWithDictionary:annotationDictionary]; + [updatedDictionary setObject:annotation.uuid forKey:@"uuid"]; + self.onAnnotationTapped(updatedDictionary); } } return self.disableDefaultActionForTappedAnnotations; @@ -348,7 +353,9 @@ - (BOOL)setAnnotationFlags:(NSString *)uuid flags:(NSArray *)flags { NSArray *allAnnotations = [[document allAnnotationsOfType:PSPDFAnnotationTypeAll].allValues valueForKeyPath:@"@unionOfArrays.self"]; for (PSPDFAnnotation *annotation in allAnnotations) { - if ([annotation.uuid isEqualToString:uuid]) { + if ([annotation.uuid isEqualToString:uuid] || + (![annotation.name isEqual:[NSNull null]] && + [annotation.name isEqualToString:uuid])) { NSUInteger convertedFlags = [RCTConvert parseAnnotationFlags:flags]; [annotation setFlags:convertedFlags]; [document.documentProviders.firstObject.annotationManager updateAnnotations:@[annotation] animated:YES]; @@ -365,7 +372,9 @@ - (BOOL)setAnnotationFlags:(NSString *)uuid flags:(NSArray *)flags { NSArray *allAnnotations = [[document allAnnotationsOfType:PSPDFAnnotationTypeAll].allValues valueForKeyPath:@"@unionOfArrays.self"]; for (PSPDFAnnotation *annotation in allAnnotations) { - if ([annotation.uuid isEqualToString:uuid]) { + if ([annotation.uuid isEqualToString:uuid] || + (![annotation.name isEqual:[NSNull null]] && + [annotation.name isEqualToString:uuid])) { PSPDFAnnotationFlags flags = annotation.flags; NSArray * convertedFlags = [RCTConvert convertAnnotationFlags:flags]; return convertedFlags; diff --git a/ios/RCTPSPDFKit/RCTPSPDFKitViewManager.m b/ios/RCTPSPDFKit/RCTPSPDFKitViewManager.m index af5fd418..7d3ac0b2 100644 --- a/ios/RCTPSPDFKit/RCTPSPDFKitViewManager.m +++ b/ios/RCTPSPDFKit/RCTPSPDFKitViewManager.m @@ -46,8 +46,12 @@ @implementation RCTPSPDFKitViewManager RCT_CUSTOM_VIEW_PROPERTY(document, PSPDFDocument, RCTPSPDFKitView) { if (json) { - view.pdfController.document = [RCTConvert PSPDFDocument:json]; + view.pdfController.document = [RCTConvert PSPDFDocument:json + remoteDocumentConfig:[_configuration objectForKey:@"remoteDocumentConfiguration"]]; view.pdfController.document.delegate = (id)view; + + PDFDocumentManager *documentManager = [self.bridge moduleForClass:[PDFDocumentManager class]]; + [documentManager setDocument:view.pdfController.document reference:view.reactTag]; // The following properties need to be set after the document is set. // We set them again here when we're certain the document exists. diff --git a/lib/annotations/Annotation.js b/lib/annotations/Annotation.js index 5bec6989..52588cba 100644 --- a/lib/annotations/Annotation.js +++ b/lib/annotations/Annotation.js @@ -55,7 +55,7 @@ exports.Annotation = Annotation; /** * If set, print the annotation when the page is printed. */ - PRINT: 'documentEditorButtonItem', + PRINT: 'print', /** * If set, don’t allow the annotation to be deleted or its properties to be modified, including contents. */ diff --git a/lib/configuration/PDFConfiguration.js b/lib/configuration/PDFConfiguration.js index e1c00d2a..33c367cd 100644 --- a/lib/configuration/PDFConfiguration.js +++ b/lib/configuration/PDFConfiguration.js @@ -1,6 +1,13 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PDFConfiguration = void 0; +/** + * The configuration when downloading a document from a remote URL. + * @typedef RemoteDocumentConfiguration + * @memberof PDFConfiguration + * @property { string } [outputFilePath] - The location where the downloaded document should be stored. If not set, the document will be stored in a temporary cache directory. + * @property { boolean } [overwriteExisting] - Whether the document should be overwritten if it already exists at the specified location. + */ /** * @namespace PDFConfiguration * @property { PDFConfiguration.ScrollDirection } [scrollDirection] Configures the direction of page scrolling in the document view. @@ -19,7 +26,7 @@ exports.PDFConfiguration = void 0; * @property { PDFConfiguration.BooleanType } [iOSAllowBackgroundSaving] Determines whether automatic saving should happen on a background thread. * @property { number } [iOSMinimumZoomScale] Minimum zoom scale for the scroll view. * @property { number } [iOSMaximumZoomScale] Maximum zoom scale for the scroll view. - * @property { PDFConfiguration.IOSDoubleTapAction } [iOSDoubleTapAction] Maximum zoom scale for the scroll view. + * @property { PDFConfiguration.IOSDoubleTapAction } [iOSDoubleTapAction] The action that happens when the user double taps somewhere in the document. * @property { PDFConfiguration.IOSTypesShowingColorPresets[] } [iOSTypesShowingColorPresets] Shows a custom cell with configurable color presets for the provided annotation types. * @property { PDFConfiguration.PageMode } [pageMode] The document PageMode. * @property { PDFConfiguration.BooleanType } [firstPageAlwaysSingle] Option to show the first page separately. @@ -93,6 +100,7 @@ exports.PDFConfiguration = void 0; * @property { number } [delay] The delay before syncing with the Instant server. * @property { PDFConfiguration.BooleanType } [syncAnnotations] Indicates whether document annotations should be synced with the Instant server. * @property { Measurements.MeasurementValueConfiguration[] } [measurementValueConfigurations] The array of ```MeasurementValueConfiguration``` objects that should be applied to the document. + * @property { PDFConfiguration.RemoteDocumentConfiguration } [remoteDocumentConfiguration] The configuration when downloading a document from a remote URL. */ var PDFConfiguration = /** @class */ (function () { function PDFConfiguration() { diff --git a/lib/document/PDFDocument.js b/lib/document/PDFDocument.js new file mode 100644 index 00000000..5b736503 --- /dev/null +++ b/lib/document/PDFDocument.js @@ -0,0 +1,54 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.PDFDocument = void 0; +var react_native_1 = require("react-native"); +/** + * @class PDFDocument + * @description The current document object loaded in the PSPDFKitView. + * + */ +var PDFDocument = /** @class */ (function () { + /** + * @ignore + */ + function PDFDocument(pdfViewRef) { + this.pdfViewRef = pdfViewRef; + } + /** + * @method getDocumentId + * @memberof PDFDocument + * @description Returns a document identifier (inferred from a document provider if possible). + * A permanent identifier based on the contents of the file at the time it was originally created. + * If a document identifier is not available, generated UID value is returned. + * @example + * const documentId = await this.pdfRef.current?.getDocument()?.getDocumentId(); + */ + PDFDocument.prototype.getDocumentId = function () { + return react_native_1.NativeModules.PDFDocumentManager.getDocumentId((0, react_native_1.findNodeHandle)(this.pdfViewRef)); + }; + /** + * @method invalidateCacheForPage + * @memberof PDFDocument + * @param {number} pageIndex Zero-based page index of the page to refresh. + * @description Invalidates the rendered cache of the given pageIndex for this document. + * Use this method if a single page of the document is not updated after a change, or changed externally, and needs to be re-rendered. + * @example + * const result = await this.pdfRef.current?.getDocument().invalidateCacheForPage(0); + */ + PDFDocument.prototype.invalidateCacheForPage = function (pageIndex) { + return react_native_1.NativeModules.PDFDocumentManager.invalidateCacheForPage((0, react_native_1.findNodeHandle)(this.pdfViewRef), pageIndex); + }; + /** + * @method invalidateCache + * @memberof PDFDocument + * @description Invalidates the rendered cache of all the pages for this document. + * Use this method if the document is not updated after a change, or changed externally, and needs to be re-rendered. + * @example + * const result = await this.pdfRef.current?.getDocument().invalidateCache(); + */ + PDFDocument.prototype.invalidateCache = function () { + return react_native_1.NativeModules.PDFDocumentManager.invalidateCache((0, react_native_1.findNodeHandle)(this.pdfViewRef)); + }; + return PDFDocument; +}()); +exports.PDFDocument = PDFDocument; diff --git a/package.json b/package.json index eedce610..649db87c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-pspdfkit", - "version": "2.10.0", + "version": "2.11.0", "description": "React Native PDF Library by PSPDFKit", "keywords": [ "react native", @@ -21,12 +21,14 @@ "start": "node node_modules/react-native/local-cli/cli.js start", "format": "prettier \"{samples/**/*.{md,js},*.{md,js}}\" --write", "lint": "eslint --ext .js,.jsx,.ts,.tsx ./*.js", - "docs": "jsdoc -c jsdoc.json", + "docs": "rm -rf jsdoc && jsdoc -c jsdoc.json", "postdocs": "node types-scripts/fix-code-tags.js", "generate-types": "npx -p typescript tsc --build tsconfig-types.json", "append": "node types-scripts/append.js", "build": "npx -p typescript tsc --build tsconfig.json", - "build-and-generate-types": "rm -rf types lib && npm run build && npm run generate-types && npm run append" + "build-and-generate-types": "rm -rf types lib && npm run build && npm run generate-types && npm run append", + "copy-types-and-lib": "cd samples/Catalog/node_modules/react-native-pspdfkit && rm -rf index.js lib src types && cd ../../../../ && cp -rf index.js lib src types samples/Catalog/node_modules/react-native-pspdfkit/", + "dev-build": "npm run build-and-generate-types && npm run docs && npm run copy-types-and-lib" }, "peerDependencies": { "@types/react": "^18.2.28", @@ -56,5 +58,20 @@ }, "engines": { "node": ">=16" - } + }, + "files": [ + "ios/", + "android/", + "lib/", + "src/", + "types/", + "windows/", + "babel.config.js", + "index.js", + "react-native-pspdfkit.podspec", + "tsconfig-types.json", + "tsconfig.json", + "package.json", + "License-Evaluation.pdf" + ] } diff --git a/samples/Catalog/.ruby-version b/samples/Catalog/.ruby-version index 37c2961c..bea438e9 100644 --- a/samples/Catalog/.ruby-version +++ b/samples/Catalog/.ruby-version @@ -1 +1 @@ -2.7.2 +3.3.1 diff --git a/samples/Catalog/Catalog.tsx b/samples/Catalog/Catalog.tsx index 181456a3..9b41c323 100644 --- a/samples/Catalog/Catalog.tsx +++ b/samples/Catalog/Catalog.tsx @@ -34,6 +34,7 @@ import { GetConfiguration } from './examples/GetConfiguration'; import { PasswordProtectedDocument } from './examples/PasswordProtectedDocument'; import { XFDF } from './examples/XFDF'; import { PSPDFKit } from './helpers/PSPDFKit'; +import { OpenRemoteDocument } from './examples/OpenRemoteDocument'; // By default, this example doesn't set a license key, but instead runs in trial mode (which is the default, // and requires SDK initialization with a null key). @@ -70,6 +71,10 @@ class Catalog extends React.Component { name="OpenImageDocument" component={OpenImageDocument} /> + diff --git a/samples/Catalog/ExamplesNavigationMenu.tsx b/samples/Catalog/ExamplesNavigationMenu.tsx index 7c6e60d2..73946dcd 100644 --- a/samples/Catalog/ExamplesNavigationMenu.tsx +++ b/samples/Catalog/ExamplesNavigationMenu.tsx @@ -50,6 +50,15 @@ export default [ }, { key: 'item3', + name: 'Open a Remote Document in a PSPDFKitView Component', + description: + 'Show how to open a remote document via URL inside a PSPDFKitView component.', + action: (component: any) => { + component.props.navigation.push('OpenRemoteDocument'); + }, + }, + { + key: 'item4', name: 'Manual Save', description: 'Add a toolbar at the bottom with a Save button and disable automatic saving.', @@ -60,7 +69,7 @@ export default [ }, }, { - key: 'item4', + key: 'item5', name: 'Save As', description: 'Save changes to the PDF in a separate file', action: (component: any) => { @@ -70,7 +79,7 @@ export default [ }, }, { - key: 'item5', + key: 'item6', name: 'Event Listeners', description: 'Show how to use the listeners exposed by the PSPDFKitView component.', @@ -79,7 +88,7 @@ export default [ }, }, { - key: 'item6', + key: 'item7', name: 'Changing the State', description: 'Add a toolbar at the bottom with buttons to toggle the annotation toolbar, and to programmatically change pages.', @@ -88,7 +97,7 @@ export default [ }, }, { - key: 'item7', + key: 'item8', name: 'Annotation Processing', description: 'Show how to embed, flatten, remove, and print annotations; then present the newly processed document.', @@ -99,7 +108,7 @@ export default [ }, }, { - key: 'item8', + key: 'item9', name: 'Programmatic Annotations', description: 'Show how to get and add new annotations using Instant JSON.', action: (component: any) => { @@ -107,7 +116,7 @@ export default [ }, }, { - key: 'item9', + key: 'item10', name: 'Programmatic Form Filling', description: 'Show how to get the value of a form element and how to programmatically fill forms.', @@ -116,7 +125,7 @@ export default [ }, }, Platform.OS === 'ios' && { - key: 'item10', + key: 'item11', name: 'Split PDF', description: 'Show two PDFs side by side by using multiple PSPDFKitView components.', @@ -125,7 +134,7 @@ export default [ }, }, { - key: 'item11', + key: 'item12', name: 'Customize the Toolbar', description: 'Show how to customize buttons in the toolbar.', action: (component: any) => { @@ -133,7 +142,7 @@ export default [ }, }, { - key: 'item12', + key: 'item13', name: 'Hidden Toolbar', description: 'Hide the main toolbar while keeping the thumbnail bar visible.', @@ -142,7 +151,7 @@ export default [ }, }, { - key: 'item13', + key: 'item14', name: 'Custom Font Picker', description: 'Show how to customize the font picker for free text annotations.', @@ -152,7 +161,7 @@ export default [ }, /// Present examples. { - key: 'item14', + key: 'item15', name: 'Open a Document Using the Native Module API', description: 'Open a document using the Native Module API by passing its path.', @@ -169,7 +178,7 @@ export default [ }, }, { - key: 'item15', + key: 'item16', name: 'Customize Document Configuration', description: 'Customize various aspects of the document by passing a configuration dictionary.', @@ -178,7 +187,7 @@ export default [ }, }, { - key: 'item16', + key: 'item17', name: 'Open an Image Document Using the Native Module API', description: 'Open an image document using the Native Module API. Supported filetypes are PNG, JPEG and TIFF.', @@ -188,7 +197,7 @@ export default [ }, }, { - key: 'item17', + key: 'item18', name: 'Generate PDF', description: 'Generate a PDF from a template, images or html', action: (component: any) => { @@ -198,7 +207,7 @@ export default [ }, }, { - key: 'item18', + key: 'item19', name: 'PSPDFKit Instant', description: 'PSPDFKit Instant synchronisation example', action: (component: any) => { @@ -208,7 +217,7 @@ export default [ }, }, { - key: 'item19', + key: 'item20', name: 'PSPDFKit Measurement', description: 'PSPDFKit measurement tools example', action: (component: any) => { @@ -218,7 +227,7 @@ export default [ }, }, { - key: 'item20', + key: 'item21', name: 'Annotation Preset customization', description: 'Customize default annotation presets and annotation menu', action: (component: any) => { @@ -228,7 +237,7 @@ export default [ }, }, { - key: 'item21', + key: 'item22', name: 'Get PDFConfiguration', description: 'Get and apply PDFConfiguration to a new instance', action: (component: any) => { @@ -238,7 +247,7 @@ export default [ }, }, { - key: 'item22', + key: 'item23', name: 'Open Password Protected PDF', description: 'Open a password protected PDF document', action: (component: any) => { @@ -248,7 +257,7 @@ export default [ }, }, { - key: 'item23', + key: 'item24', name: 'XFDF Import and Export', description: 'Import and export annotations from XFDF files', action: (component: any) => { @@ -258,7 +267,6 @@ export default [ }); }); }, - }, ]; diff --git a/samples/Catalog/android/app/build.gradle b/samples/Catalog/android/app/build.gradle index 9ba73452..8edd994c 100644 --- a/samples/Catalog/android/app/build.gradle +++ b/samples/Catalog/android/app/build.gradle @@ -115,14 +115,6 @@ android { include (*reactNativeArchitectures()) } } - signingConfigs { - debug { - storeFile file('debug.keystore') - storePassword 'android' - keyAlias 'androiddebugkey' - keyPassword 'android' - } - } buildTypes { debug { signingConfig signingConfigs.debug diff --git a/samples/Catalog/examples/OpenRemoteDocument.tsx b/samples/Catalog/examples/OpenRemoteDocument.tsx new file mode 100644 index 00000000..76a03311 --- /dev/null +++ b/samples/Catalog/examples/OpenRemoteDocument.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Alert, Button, processColor, View } from 'react-native'; +import PSPDFKitView, { RemoteDocumentConfiguration } from 'react-native-pspdfkit'; + +import { exampleDocumentPath, pspdfkitColor } from '../configuration/Constants'; +import { BaseExampleAutoHidingHeaderComponent } from '../helpers/BaseExampleAutoHidingHeaderComponent'; +import { hideToolbar } from '../helpers/NavigationHelper'; +import RNFS from 'react-native-fs'; + +export class OpenRemoteDocument extends BaseExampleAutoHidingHeaderComponent { + pdfRef: React.RefObject; + + constructor(props: any) { + super(props); + const { navigation } = this.props; + this.pdfRef = React.createRef(); + hideToolbar(navigation); + } + + override render() { + const { navigation } = this.props; + const myDocumentPath = RNFS.TemporaryDirectoryPath + '/test.pdf'; + + return ( + + navigation.goBack()} + style={styles.pdfColor} + /> + + +