Browse Source

ComPDFKit(flutter) - 新增水印、打开功能界面接口

liuxiaolong 2 tháng trước cách đây
mục cha
commit
83ba0d2c9c

+ 2 - 1
android/build.gradle

@@ -16,6 +16,7 @@ rootProject.allprojects {
     repositories {
         google()
         mavenCentral()
+        mavenLocal()
     }
 }
 
@@ -40,7 +41,7 @@ android {
         implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
 
         // dependencies compdfkit pdf sdk
-        api 'com.compdf:compdfkit-tools:2.2.0'
+        api 'com.compdf:compdfkit-tools:2.2.1-SNAPSHOT'
 
         testImplementation 'junit:junit:4.13.2'
         testImplementation 'org.mockito:mockito-core:5.0.0'

+ 32 - 0
android/src/main/java/com/compdfkit/flutter/compdfkit_flutter/constants/CPDFConstants.java

@@ -117,6 +117,38 @@ public class CPDFConstants {
     public static final String REMOVE_ALL_ANNOTATIONS = "remove_all_annotations";
 
     public static final String GET_PAGE_COUNT = "get_page_count";
+
+    public static final String SET_PREVIEW_MODE = "set_preview_mode";
+
+    public static final String GET_PREVIEW_MODE = "get_preview_mode";
+
+    public static final String SHOW_THUMBNAIL_VIEW = "show_thumbnail_view";
+
+    public static final String SHOW_BOTA_VIEW = "show_bota_view";
+
+    public static final String SHOW_ADD_WATERMARK_VIEW = "show_add_watermark_view";
+
+    public static final String SHOW_SECURITY_VIEW = "show_security_view";
+
+    public static final String SHOW_DISPLAY_SETTINGS_VIEW = "show_display_settings_view";
+
+    public static final String ENTER_SNIP_MODE = "enter_snip_mode";
+
+    public static final String EXIT_SNIP_MODE = "exit_snip_mode";
+
+    public static final String SET_SECURITY_INFO = "set_security_info";
+
+    public static final String SAVE_AS = "save_as";
+
+    public static final String PRINT = "print";
+
+    public static final String CREATE_URI = "create_uri";
+    public static final String SET_USER_PASSWORD = "set_user_password";
+    public static final String REMOVE_PASSWORD = "remove_password";
+    public static final String SET_PASSWORD = "set_password";
+    public static final String CREATE_WATERMARK = "create_watermark";
+    public static final String REMOVE_ALL_WATERMARKS = "remove_all_watermarks";
+    public static final String GET_ENCRYPT_ALGORITHM = "get_encrypt_algorithm";
   }
 
 }

+ 268 - 5
android/src/main/java/com/compdfkit/flutter/compdfkit_flutter/plugin/CPDFDocumentPlugin.java

@@ -14,7 +14,10 @@ package com.compdfkit.flutter.compdfkit_flutter.plugin;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.CHECK_OWNER_UNLOCKED;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.CHECK_OWNER_PASSWORD;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.CLOSE;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.CREATE_WATERMARK;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_ENCRYPT_ALGORITHM;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_PAGE_COUNT;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.PRINT;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.REMOVE_ALL_ANNOTATIONS;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.EXPORT_ANNOTATIONS;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_FILE_NAME;
@@ -24,15 +27,34 @@ import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.Ch
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.IS_ENCRYPTED;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.IS_IMAGE_DOC;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.OPEN_DOCUMENT;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.REMOVE_ALL_WATERMARKS;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.REMOVE_PASSWORD;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SAVE;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SAVE_AS;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_PASSWORD;
 
 import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.net.Uri;
+import android.os.Environment;
 import android.text.TextUtils;
+import android.util.Log;
 import androidx.annotation.NonNull;
+import com.compdfkit.core.common.CPDFDocumentException;
 import com.compdfkit.core.document.CPDFDocument;
+import com.compdfkit.core.document.CPDFDocument.PDFDocumentEncryptAlgo;
 import com.compdfkit.core.document.CPDFDocument.PDFDocumentError;
+import com.compdfkit.core.document.CPDFDocument.PDFDocumentSaveType;
+import com.compdfkit.core.document.CPDFDocumentPermissionInfo;
+import com.compdfkit.core.watermark.CPDFWatermark;
+import com.compdfkit.core.watermark.CPDFWatermark.Horizalign;
+import com.compdfkit.core.watermark.CPDFWatermark.Type;
+import com.compdfkit.core.watermark.CPDFWatermark.Vertalign;
 import com.compdfkit.flutter.compdfkit_flutter.utils.FileUtils;
 import com.compdfkit.tools.common.utils.CFileUtils;
+import com.compdfkit.tools.common.utils.image.CBitmapUtil;
 import com.compdfkit.tools.common.utils.threadpools.CThreadPoolUtils;
 import com.compdfkit.tools.common.views.pdfview.CPDFViewCtrl;
 import com.compdfkit.ui.reader.CPDFReaderView;
@@ -40,6 +62,7 @@ import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
 import io.flutter.plugin.common.MethodChannel.Result;
 import java.io.File;
+import java.util.Objects;
 
 public class CPDFDocumentPlugin extends BaseMethodChannelPlugin {
 
@@ -76,12 +99,13 @@ public class CPDFDocumentPlugin extends BaseMethodChannelPlugin {
         String filePath = call.argument("filePath");
         String openPwd = call.argument("password");
         PDFDocumentError error;
-        if (filePath.startsWith(FileUtils.CONTENT_SCHEME) || filePath.startsWith(FileUtils.FILE_SCHEME)){
-          pdfView.openPDF(Uri.parse(filePath), openPwd, ()->{
+        if (filePath.startsWith(FileUtils.CONTENT_SCHEME) || filePath.startsWith(
+            FileUtils.FILE_SCHEME)) {
+          pdfView.openPDF(Uri.parse(filePath), openPwd, () -> {
             result.success(true);
           });
-        }else {
-          pdfView.openPDF(filePath,openPwd, ()->{
+        } else {
+          pdfView.openPDF(filePath, openPwd, () -> {
             result.success(true);
           });
         }
@@ -107,6 +131,7 @@ public class CPDFDocumentPlugin extends BaseMethodChannelPlugin {
       case CHECK_OWNER_PASSWORD:
         String password = call.argument("password");
         result.success(document.checkOwnerPassword(password));
+        break;
       case CLOSE:
         document.close();
         result.success(true);
@@ -116,7 +141,8 @@ public class CPDFDocumentPlugin extends BaseMethodChannelPlugin {
         break;
       case IMPORT_ANNOTATIONS:
         try {
-          String xfdfFilePath = FileUtils.getImportAnnotationsPath(context,  (String) call.arguments);
+          String xfdfFilePath = FileUtils.getImportAnnotationsPath(context,
+              (String) call.arguments);
           File file = new File(xfdfFilePath);
           if (!file.exists()) {
             result.success(false);
@@ -167,8 +193,245 @@ public class CPDFDocumentPlugin extends BaseMethodChannelPlugin {
       case GET_PAGE_COUNT:
         result.success(document.getPageCount());
         break;
+      case SAVE:
+        pdfView.savePDF((s, uri) -> {
+          Log.e(LOG_TAG, "CPDFViewCtrlPlugin:onMethodCall:save-success");
+          result.success(true);
+        }, e -> {
+          Log.e(LOG_TAG, "CPDFViewCtrlPlugin:onMethodCall:save-fail");
+          result.success(false);
+        });
+        break;
+      case SAVE_AS:
+        String savePath = call.argument("save_path");
+        boolean removeSecurity = call.argument("remove_security");
+        boolean fontSubSet = call.argument("font_sub_set");
+        CThreadPoolUtils.getInstance().executeIO(() -> {
+          try {
+            boolean saveResult;
+            if (savePath.startsWith(FileUtils.CONTENT_SCHEME)) {
+              saveResult = document.saveAs(Uri.parse(savePath), removeSecurity, fontSubSet);
+            } else {
+              saveResult = document.saveAs(savePath, removeSecurity, false, fontSubSet);
+            }
+            if (document.shouleReloadDocument()) {
+              document.reload();
+            }
+            result.success(saveResult);
+          } catch (CPDFDocumentException e) {
+            e.printStackTrace();
+            result.error("SAVE_FAIL",
+                "The current saved directory is: " + savePath
+                    + ", please make sure you have write permission to this directory", "");
+          }
+        });
+        break;
+      case PRINT:
+        String path = readerView.getPDFDocument().getAbsolutePath();
+        Uri uri = readerView.getPDFDocument().getUri();
+        CFileUtils.startPrint(context, path, uri);
+        break;
+      case REMOVE_PASSWORD:
+        CThreadPoolUtils.getInstance().executeIO(() -> {
+          try {
+            boolean saveResult = document.save(PDFDocumentSaveType.PDFDocumentSaveRemoveSecurity,
+                true);
+            result.success(saveResult);
+            if (document.shouleReloadDocument()) {
+              document.reload();
+            }
+          } catch (Exception e) {
+            result.error("SAVE_FAIL",
+                "An exception occurs when remove document opening password and saving it.,"
+                    + e.getMessage(), "");
+          }
+        });
+        break;
+      case SET_PASSWORD:
+        CThreadPoolUtils.getInstance().executeIO(() -> {
+          try {
+            String userPassword = call.argument("user_password");
+            String ownerPassword = call.argument("owner_password");
+            boolean allowsPrinting = call.argument("allows_printing");
+            boolean allowsCopying = call.argument("allows_copying");
+            String encryptAlgo = call.argument("encrypt_algo");
+
+            if (!TextUtils.isEmpty(userPassword)) {
+              document.setUserPassword(userPassword);
+            }
+            if (!TextUtils.isEmpty(ownerPassword)) {
+              document.setOwnerPassword(ownerPassword);
+              CPDFDocumentPermissionInfo permissionInfo = document.getPermissionsInfo();
+              permissionInfo.setAllowsPrinting(allowsPrinting);
+              permissionInfo.setAllowsCopying(allowsCopying);
+              document.setPermissionsInfo(permissionInfo);
+            }
+
+            switch (encryptAlgo) {
+              case "rc4":
+                document.setEncryptAlgorithm(PDFDocumentEncryptAlgo.PDFDocumentRC4);
+                break;
+              case "aes128":
+                document.setEncryptAlgorithm(PDFDocumentEncryptAlgo.PDFDocumentAES128);
+                break;
+              case "aes256":
+                document.setEncryptAlgorithm(PDFDocumentEncryptAlgo.PDFDocumentAES256);
+                break;
+              case "noEncryptAlgo":
+                document.setEncryptAlgorithm(PDFDocumentEncryptAlgo.PDFDocumentNoEncryptAlgo);
+                break;
+              default:
+                break;
+            }
+
+            boolean saveResult = document.save(
+                CPDFDocument.PDFDocumentSaveType.PDFDocumentSaveIncremental, true);
+
+            if (document.shouleReloadDocument()) {
+              if (!TextUtils.isEmpty(userPassword)) {
+                document.reload(userPassword);
+              } else if (!TextUtils.isEmpty(ownerPassword)) {
+                document.reload(ownerPassword);
+              } else {
+                document.reload();
+              }
+            }
+            result.success(saveResult);
+          } catch (Exception e) {
+            result.error("SAVE_FAIL",
+                "An exception occurs when setting a document opening password and saving it.,"
+                    + e.getMessage(), "");
+          }
+        });
+        break;
+      case GET_ENCRYPT_ALGORITHM:
+        switch (document.getEncryptAlgorithm()) {
+          case PDFDocumentRC4:
+            result.success("rc4");
+            break;
+          case PDFDocumentAES128:
+            result.success("aes128");
+            break;
+          case PDFDocumentAES256:
+            result.success("aes256");
+            break;
+          case PDFDocumentNoEncryptAlgo:
+            result.success("noEncryptAlgo");
+            break;
+        }
+        break;
+      case CREATE_WATERMARK:
+        Object object = call.arguments;
+        Log.e("ComPDFKit-Flutter", "watermark:" + object.toString());
+        createWatermark(call, result, pdfView, document);
+        break;
+      case REMOVE_ALL_WATERMARKS:
+        for (int watermarkCount = document.getWatermarkCount(); watermarkCount > 0;
+            watermarkCount--) {
+          document.getWatermark(watermarkCount -1).clear();
+        }
+        pdfView.getCPdfReaderView().reloadPages();
+        break;
       default:
         break;
     }
   }
+
+  private void createWatermark(MethodCall call, Result result, CPDFViewCtrl pdfView,
+      CPDFDocument document) {
+    String type = call.argument("type");
+    String textContent = call.argument("text_content");
+    String imagePath = call.argument("image_path");
+    String textColor = call.argument("text_color");
+    int fontSize = call.argument("font_size");
+    double scaleDouble = call.argument("scale");
+    float scale = (float) scaleDouble;
+    double rotationDouble = call.argument("rotation");
+    float rotation = (float) rotationDouble;
+    double opacityDouble = call.argument("opacity");
+    float opacity = (float) opacityDouble;
+    String verticalAlignment = call.argument("vertical_alignment");
+    String horizontalAlignment = call.argument("horizontal_alignment");
+    double verticalOffsetDouble = call.argument("vertical_offset");
+    float verticalOffset = (float) verticalOffsetDouble;
+    double horizontalOffsetDouble = call.argument("horizontal_offset");
+    float horizontalOffset = (float) horizontalOffsetDouble;
+    String pages = call.argument("pages");
+    boolean isFront = call.argument("is_front");
+    boolean isTilePage = call.argument("is_tile_page");
+    double horizontalSpacingDouble = call.argument("horizontal_spacing");
+    float horizontalSpacing = (float) horizontalSpacingDouble;
+    double verticalSpacingDouble = call.argument("vertical_spacing");
+    float verticalSpacing = (float) verticalSpacingDouble;
+
+    if (TextUtils.isEmpty(pages)){
+      result.error("WATERMARK_FAIL",
+          "The page range cannot be empty, please set the page range, for example: pages: \"0,1,2,3\"", "");
+      return;
+    }
+    if ("image".equals(type) && TextUtils.isEmpty(imagePath)) {
+      Log.e("ComPDFKit-Flutter", "image path:" + imagePath);
+      result.error("WATERMARK_FAIL", "image path is empty.", "");
+      return;
+    }
+    CPDFWatermark watermark;
+    if ("text".equals(type)) {
+      watermark = document.createWatermark(Type.WATERMARK_TYPE_TEXT);
+      watermark.setText(textContent);
+      watermark.setFontName("Helvetica");
+      watermark.setTextRGBColor(Color.parseColor(textColor));
+      watermark.setFontSize(fontSize);
+    } else {
+      Bitmap bitmap = CBitmapUtil.decodeBitmap(imagePath);
+      if (bitmap == null) {
+        result.error("WATERMARK_FAIL", "image path is invalid. bitmap == null", "");
+        return;
+      }
+      watermark = document.createWatermark(Type.WATERMARK_TYPE_IMG);
+      watermark.setImage(bitmap, 100, 200);
+    }
+
+    watermark.setOpacity(opacity);
+    watermark.setFront(isFront);
+
+    switch (verticalAlignment) {
+      case "top":
+        watermark.setVertalign(Vertalign.WATERMARK_VERTALIGN_TOP);
+        break;
+      case "center":
+        watermark.setVertalign(Vertalign.WATERMARK_VERTALIGN_CENTER);
+        break;
+      case "bottom":
+        watermark.setVertalign(Vertalign.WATERMARK_VERTALIGN_BOTTOM);
+        break;
+    }
+
+    switch (horizontalAlignment) {
+      case "left":
+        watermark.setHorizalign(Horizalign.WATERMARK_HORIZALIGN_LEFT);
+        break;
+      case "center":
+        watermark.setHorizalign(Horizalign.WATERMARK_HORIZALIGN_CENTER);
+        break;
+      case "right":
+        watermark.setHorizalign(Horizalign.WATERMARK_HORIZALIGN_RIGHT);
+        break;
+    }
+    watermark.setRotation(rotation);
+    watermark.setVertOffset(
+        verticalOffset);// Translation offset relative to the vertical position. Positive values move downward, while negative values move upward.
+    watermark.setHorizOffset(
+        horizontalOffset);// Translation offset relative to the horizontal position. Positive values move to the right, while negative values move to the left.
+    watermark.setScale(
+        scale);// Scaling factor for the watermark, with a default value of 1. If it is an image watermark, 1 represents the original size of the image. If it is a text watermark, 1 represents the `textFont` font size.
+    watermark.setPages(pages);// Set the watermark for pages 3, 4, and 5.
+    watermark.setFullScreen(
+        isTilePage);// Enable watermark tiling (not applicable for image watermarks).
+    watermark.setHorizontalSpacing(
+        horizontalSpacing);// Set the horizontal spacing for tiled watermarks.
+    watermark.setVerticalSpacing(verticalSpacing);// Set the vertical spacing for tiled watermarks.
+    watermark.update();
+    watermark.release();
+    pdfView.getCPdfReaderView().reloadPages();
+  }
 }

+ 44 - 14
android/src/main/java/com/compdfkit/flutter/compdfkit_flutter/plugin/CPDFViewCtrlPlugin.java

@@ -10,8 +10,11 @@
 
 package com.compdfkit.flutter.compdfkit_flutter.plugin;
 
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.ENTER_SNIP_MODE;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.EXIT_SNIP_MODE;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_CURRENT_PAGE_INDEX;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_PAGE_SIZE;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_PREVIEW_MODE;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_READ_BACKGROUND_COLOR;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_SCALE;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.IS_CONTINUE_MODE;
@@ -22,6 +25,11 @@ import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.Ch
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.IS_LINK_HIGHLIGHT;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.IS_PAGE_IN_SCREEN;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.IS_VERTICAL_MODE;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SHOW_ADD_WATERMARK_VIEW;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SHOW_BOTA_VIEW;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SHOW_DISPLAY_SETTINGS_VIEW;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SHOW_SECURITY_VIEW;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SHOW_THUMBNAIL_VIEW;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SAVE;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_CAN_SCALE;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_CONTINUE_MODE;
@@ -35,6 +43,7 @@ import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.Ch
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_PAGE_SAME_WIDTH;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_MARGIN;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_PAGE_SPACING;
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_PREVIEW_MODE;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_READ_BACKGROUND_COLOR;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_SCALE;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.SET_VERTICAL_MODE;
@@ -46,11 +55,11 @@ import android.util.Log;
 
 import androidx.annotation.NonNull;
 
-import com.compdfkit.core.document.CPDFDocument;
-
 import com.compdfkit.tools.common.pdf.CPDFDocumentFragment;
 import com.compdfkit.tools.common.utils.viewutils.CViewUtils;
 import com.compdfkit.tools.common.views.pdfview.CPDFIReaderViewCallback;
+import com.compdfkit.tools.common.views.pdfview.CPDFViewCtrl;
+import com.compdfkit.tools.common.views.pdfview.CPreviewMode;
 import com.compdfkit.ui.reader.CPDFReaderView;
 import io.flutter.plugin.common.BinaryMessenger;
 import io.flutter.plugin.common.MethodCall;
@@ -116,18 +125,9 @@ public class CPDFViewCtrlPlugin extends BaseMethodChannelPlugin {
           "CPDFViewCtrlFlutter:onMethodCall: documentFragment is Null return not implemented.");
       result.notImplemented();
     }
-
-    CPDFReaderView readerView = documentFragment.pdfView.getCPdfReaderView();
+    CPDFViewCtrl pdfView = documentFragment.pdfView;
+    CPDFReaderView readerView = pdfView.getCPdfReaderView();
     switch (call.method) {
-      case SAVE:
-        documentFragment.pdfView.savePDF((s, uri) -> {
-          Log.e(LOG_TAG, "CPDFViewCtrlPlugin:onMethodCall:save-success");
-          result.success(true);
-        }, e -> {
-          Log.e(LOG_TAG, "CPDFViewCtrlPlugin:onMethodCall:save-fail");
-          result.success(false);
-        });
-        break;
       case SET_SCALE:
         double scaleValue = (double) call.arguments;
         readerView.setScale((float) scaleValue);
@@ -142,7 +142,7 @@ public class CPDFViewCtrlPlugin extends BaseMethodChannelPlugin {
       case SET_READ_BACKGROUND_COLOR:
         String colorHex = call.argument("color");
         readerView.setReadBackgroundColor(Color.parseColor(colorHex));
-        documentFragment.pdfView.setBackgroundColor(
+        pdfView.setBackgroundColor(
             CViewUtils.getColor(Color.parseColor(colorHex), 190));
         break;
       case GET_READ_BACKGROUND_COLOR:
@@ -241,6 +241,36 @@ public class CPDFViewCtrlPlugin extends BaseMethodChannelPlugin {
         pageSizeMap.put("height", rectF.height());
         result.success(pageSizeMap);
         break;
+      case SET_PREVIEW_MODE:
+        String alias = (String) call.arguments;
+        CPreviewMode previewMode = CPreviewMode.fromAlias(alias);
+        documentFragment.setPreviewMode(previewMode);
+        break;
+      case GET_PREVIEW_MODE:
+        result.success(documentFragment.pdfToolBar.getMode().alias);
+        break;
+      case SHOW_THUMBNAIL_VIEW:
+        boolean enterEditMode = (boolean) call.arguments;
+        documentFragment.showPageEdit(enterEditMode);
+        break;
+      case SHOW_BOTA_VIEW:
+        documentFragment.showBOTA();
+        break;
+      case SHOW_ADD_WATERMARK_VIEW:
+        documentFragment.showAddWatermarkDialog();
+        break;
+      case SHOW_SECURITY_VIEW:
+        documentFragment.showSecurityDialog();
+        break;
+      case SHOW_DISPLAY_SETTINGS_VIEW:
+        documentFragment.showDisplaySettings(pdfView);
+        break;
+      case ENTER_SNIP_MODE:
+        documentFragment.enterSnipMode();
+        break;
+      case EXIT_SNIP_MODE:
+        documentFragment.exitScreenShot();
+        break;
       default:
         Log.e(LOG_TAG, "CPDFViewCtrlFlutter:onMethodCall:notImplemented");
         result.notImplemented();

+ 21 - 0
android/src/main/java/com/compdfkit/flutter/compdfkit_flutter/plugin/ComPDFKitSDKPlugin.java

@@ -9,6 +9,7 @@
 
 package com.compdfkit.flutter.compdfkit_flutter.plugin;
 
+import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.CREATE_URI;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.GET_TEMP_DIRECTORY;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.INIT_SDK;
 import static com.compdfkit.flutter.compdfkit_flutter.constants.CPDFConstants.ChannelMethod.INIT_SDK_KEYS;
@@ -22,6 +23,8 @@ import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
+import android.os.Environment;
+import android.text.TextUtils;
 import android.util.Log;
 
 import androidx.annotation.NonNull;
@@ -34,6 +37,7 @@ import com.compdfkit.tools.common.pdf.CPDFDocumentActivity;
 import com.compdfkit.tools.common.pdf.config.CPDFConfiguration;
 import com.compdfkit.tools.common.utils.CFileUtils;
 
+import com.compdfkit.tools.common.utils.CUriUtil;
 import io.flutter.plugin.common.PluginRegistry;
 import java.io.File;
 
@@ -106,6 +110,23 @@ public class ComPDFKitSDKPlugin extends BaseMethodChannelPlugin implements Plugi
                     activity.startActivityForResult(CFileUtils.getContentIntent(), REQUEST_CODE);
                 }
                 break;
+            case CREATE_URI:
+                String fileName = call.argument("file_name");
+                String mimeType = call.argument("mime_type");
+                String childDirectoryName = call.argument("child_directory_name");
+                String dir = Environment.DIRECTORY_DOWNLOADS ;
+                if (!TextUtils.isEmpty(childDirectoryName)){
+                    dir += File.separator + childDirectoryName;
+                }
+                Uri uri = CUriUtil.createFileUri(context,
+                    dir,
+                    fileName, mimeType);
+                if (uri != null){
+                    result.success(uri.toString());
+                }else {
+                    result.error("CREATE_URI_FAIL", "create uri fail", "");
+                }
+                break;
             default:
                 break;
         }

+ 1 - 1
example/android/settings.gradle

@@ -23,4 +23,4 @@ plugins {
     id "org.jetbrains.kotlin.android" version "1.7.10" apply false
 }
 
-include ":app"
+include ":app"

BIN
example/images/logo.png


+ 137 - 57
example/lib/cpdf_reader_widget_controller_example.dart

@@ -14,13 +14,15 @@ import 'package:compdfkit_flutter/configuration/cpdf_options.dart';
 import 'package:compdfkit_flutter/util/extension/cpdf_color_extension.dart';
 import 'package:compdfkit_flutter/widgets/cpdf_reader_widget.dart';
 import 'package:compdfkit_flutter/widgets/cpdf_reader_widget_controller.dart';
+import 'package:compdfkit_flutter_example/page/cpdf_reader_widget_switch_preview_mode_page.dart';
 import 'package:compdfkit_flutter_example/utils/file_util.dart';
 import 'package:flutter/material.dart';
 
 class CPDFReaderWidgetControllerExample extends StatefulWidget {
   final String documentPath;
 
-  const CPDFReaderWidgetControllerExample({super.key, required this.documentPath});
+  const CPDFReaderWidgetControllerExample(
+      {super.key, required this.documentPath});
 
   @override
   State<CPDFReaderWidgetControllerExample> createState() =>
@@ -29,7 +31,6 @@ class CPDFReaderWidgetControllerExample extends StatefulWidget {
 
 class _CPDFReaderWidgetControllerExampleState
     extends State<CPDFReaderWidgetControllerExample> {
-
   CPDFReaderWidgetController? _controller;
 
   bool canScale = true;
@@ -50,32 +51,24 @@ class _CPDFReaderWidgetControllerExampleState
                 Navigator.pop(context);
               },
               icon: const Icon(Icons.arrow_back)),
-          actions: null == _controller
-              ? []
-              : [
-                  PopupMenuButton(onSelected: (value) {
-                    handleClick(value, _controller!);
-                  }, itemBuilder: (context) {
-                    return actions
-                        .map((e) => PopupMenuItem(value: e, child: Text(e)))
-                        .toList();
-                  })
-                ],
+          actions: null == _controller ? null : _buildAppBarActions(context),
         ),
         body: CPDFReaderWidget(
           document: widget.documentPath,
-          configuration: CPDFConfiguration(toolbarConfig: const CPDFToolbarConfig(
-            iosLeftBarAvailableActions: [CPDFToolbarAction.thumbnail]
-          )),
+          configuration: CPDFConfiguration(
+              toolbarConfig: const CPDFToolbarConfig(
+                  iosLeftBarAvailableActions: [
+                    CPDFToolbarAction.thumbnail
+                  ])),
           onCreated: (controller) {
             setState(() {
               _controller = controller;
             });
           },
-          onPageChanged: (pageIndex){
+          onPageChanged: (pageIndex) {
             debugPrint('pageIndex:$pageIndex');
           },
-          onSaveCallback: (){
+          onSaveCallback: () {
             debugPrint('CPDFDocument: save success');
           },
         ));
@@ -86,16 +79,66 @@ class _CPDFReaderWidgetControllerExampleState
     debugPrint('ComPDFKit-Flutter: saveResult:$saveResult');
   }
 
+  List<Widget> _buildAppBarActions(BuildContext context) {
+    return [
+      PopupMenuButton<String>(
+        icon: Icon(Icons.settings),
+        onSelected: (value) {
+          handleClick(value, _controller!);
+        },
+        itemBuilder: (context) {
+          return actions1.map((action) {
+            return PopupMenuItem(
+              value: action,
+              child: Text(action),
+            );
+          }).toList();
+        },
+      ),
+      PopupMenuButton<String>(
+        onSelected: (value) {
+          handleClick(value, _controller!);
+        },
+        itemBuilder: (context) {
+          return actions.map((action) {
+            return PopupMenuItem(
+              value: action,
+              child: Text(action),
+            );
+          }).toList();
+        },
+      ),
+    ];
+  }
+
   void handleClick(String value, CPDFReaderWidgetController controller) async {
     switch (value) {
       case 'save':
-        bool saveResult = await controller.save();
+        bool saveResult = await controller.document.save();
         debugPrint('ComPDFKit: save():$saveResult');
         break;
+      case 'saveAs':
+        // String? directory = await FilePicker.platform.getDirectoryPath();
+        // if (directory != null) {
+        //   debugPrint('ComPDFKit: filePick:$directory');
+        //   String savePath = '${directory}/temp/${await controller.document.getFileName()}';
+        //   debugPrint('ComPDFKit:saveAs:$savePath');
+        //   bool saveResult = await controller.document.saveAs(savePath);
+        //   debugPrint('ComPDFKit:saveAs:Result:$saveResult');
+        // }
+
+        final tempDir = await ComPDFKit.getTemporaryDirectory();
+        String savePath = '${tempDir.path}/temp/${await controller.document.getFileName()}';
+      // only android platform
+      //   String? savePath = await ComPDFKit.createUri('aaa.pdf', childDirectoryName: 'compdfkit');
+        if (savePath != null) {
+          debugPrint('ComPDFKit:saveAs:$savePath');
+          bool saveResult = await controller.document.saveAs(savePath);
+          debugPrint('ComPDFKit:saveAs:Result:$saveResult');
+        }
+        break;
       case 'setScale':
         controller.setScale(1.5);
-        break;
-      case 'getScale':
         double scaleValue = await controller.getScale();
         debugPrint('ComPDFKit:CPDFReaderWidget-getScale():$scaleValue');
         break;
@@ -122,7 +165,8 @@ class _CPDFReaderWidgetControllerExampleState
         final Random random = Random();
         int value = random.nextInt(50);
         debugPrint('ComPDFKit:setMargin:$value');
-        controller.setMargins(const CPDFEdgeInsets.only(left: 20, top:20, right: 20, bottom: 20));
+        controller.setMargins(const CPDFEdgeInsets.only(
+            left: 20, top: 20, right: 20, bottom: 20));
         break;
       case "setPageSpacing":
         await controller.setPageSpacing(20);
@@ -143,31 +187,19 @@ class _CPDFReaderWidgetControllerExampleState
         controller.setCropMode(!isCropMode);
         break;
       case 'setDisplayPageIndex':
-        int nextPageIndex = await controller.getCurrentPageIndex() + 1;
+        int currentPageIndex = await controller.getCurrentPageIndex();
+        debugPrint('ComPDFKit:getCurrentPageIndex:${currentPageIndex}');
+        int nextPageIndex = currentPageIndex + 1;
         controller.setDisplayPageIndex(nextPageIndex, animated: true);
         break;
-      case 'getCurrentPageIndex':
-        debugPrint('ComPDFKit:getCurrentPageIndex:${await controller.getCurrentPageIndex()}');
-        break;
       case 'setCoverPageMode':
         bool isCoverPageMode = await controller.isCoverPageMode();
         debugPrint('ComPDFKit:isCoverPageMode:$isCoverPageMode');
         controller.setCoverPageMode(!isCoverPageMode);
         break;
-      case 'pageSameWidth':
-        pageSameWidth = !pageSameWidth;
-        await controller.setPageSameWidth(pageSameWidth);
-        break;
-      case 'isPageInScreen':
-        bool isPageInScreen = await controller.isPageInScreen(0);
-        debugPrint('ComPDFKit:first page is in screen:$isPageInScreen');
-        break;
-      case 'setFixedScroll':
-        isFixedScroll = !isFixedScroll;
-        await controller.setFixedScroll(isFixedScroll);
-        break;
       case 'setReadBackgroundColor':
-        var currentReadBackgroundColor = await controller.getReadBackgroundColor();
+        var currentReadBackgroundColor =
+            await controller.getReadBackgroundColor();
         debugPrint('readBackgroundColor:${currentReadBackgroundColor.toHex()}');
         await controller.setReadBackgroundColor(CPDFThemes.dark);
         break;
@@ -177,13 +209,20 @@ class _CPDFReaderWidgetControllerExampleState
         break;
       case "documentInfo":
         var document = controller.document;
-        debugPrint('ComPDFKit:Document: fileName:${await document.getFileName()}');
-        debugPrint('ComPDFKit:Document: checkOwnerUnlocked:${await document.checkOwnerUnlocked()}');
-        debugPrint('ComPDFKit:Document: hasChange:${await document.hasChange()}');
-        debugPrint('ComPDFKit:Document: isEncrypted:${await document.isEncrypted()}');
-        debugPrint('ComPDFKit:Document: isImageDoc:${await document.isImageDoc()}');
-        debugPrint('ComPDFKit:Document: getPermissions:${await document.getPermissions()}');
-        debugPrint('ComPDFKit:Document: getPageCount:${await document.getPageCount()}');
+        debugPrint(
+            'ComPDFKit:Document: fileName:${await document.getFileName()}');
+        debugPrint(
+            'ComPDFKit:Document: checkOwnerUnlocked:${await document.checkOwnerUnlocked()}');
+        debugPrint(
+            'ComPDFKit:Document: hasChange:${await document.hasChange()}');
+        debugPrint(
+            'ComPDFKit:Document: isEncrypted:${await document.isEncrypted()}');
+        debugPrint(
+            'ComPDFKit:Document: isImageDoc:${await document.isImageDoc()}');
+        debugPrint(
+            'ComPDFKit:Document: getPermissions:${await document.getPermissions()}');
+        debugPrint(
+            'ComPDFKit:Document: getPageCount:${await document.getPageCount()}');
         break;
       case "openDocument":
         String? path = await ComPDFKit.pickFile();
@@ -202,7 +241,8 @@ class _CPDFReaderWidgetControllerExampleState
         // android Uri:
         //String xfdfFile = "content://xxx";
 
-        bool result = await controller.document.importAnnotations(xfdfFile.path);
+        bool result =
+            await controller.document.importAnnotations(xfdfFile.path);
         debugPrint('ComPDFKit:Document: importAnnotations:$result');
         break;
       case "exportAnnotations":
@@ -216,20 +256,50 @@ class _CPDFReaderWidgetControllerExampleState
         bool result = await ComPDFKit.removeSignFileList();
         debugPrint('ComPDFKit:removeSignFileList:$result');
         break;
+      case "PreviewMode":
+        CPDFViewMode mode = await controller.getPreviewMode();
+        CPDFViewMode? switchMode = await showModalBottomSheet(
+            context: context,
+            builder: (context) {
+              return CpdfReaderWidgetSwitchPreviewModePage(viewMode: mode);
+            });
+        if (switchMode != null) {
+          await controller.setPreviewMode(switchMode);
+        }
+        break;
+      case 'DisplaySetting':
+        await controller.showDisplaySettingView();
+        break;
+      case 'Watermark':
+        await controller.showAddWatermarkView();
+        break;
+      case 'Security':
+        await controller.showSecurityView();
+        break;
+      case 'Thumbnail':
+        await controller.showThumbnailView(true);
+        break;
+      case 'BOTA':
+        await controller.showBotaView();
+        break;
+      case 'SnipMode':
+        await controller.enterSnipMode();
+        break;
+      case 'ExitSnipMode':
+        await controller.exitSnipMode();
+        break;
+      case 'print':
+        await controller.document.printDocument();
+        break;
     }
   }
 }
 
 var actions = [
   'save',
+  'saveAs',
+  'openDocument',
   'setScale',
-  'getScale',
-  if(Platform.isAndroid) ...[
-    'setCanScale',
-    'pageSameWidth',
-    'isPageInScreen',
-    'setFixedScroll',
-  ],
   'setPageSpacing',
   'setReadBackgroundColor',
   'setFormHighlight',
@@ -240,15 +310,25 @@ var actions = [
   'setDoublePageMode',
   'setCropMode',
   'setDisplayPageIndex',
-  'getCurrentPageIndex',
   'setCoverPageMode',
   'isChanged',
   'documentInfo',
-  'openDocument',
   'importAnnotations',
   'exportAnnotations',
   'removeAllAnnotations',
-  'removeSignFileList'
+  'removeSignFileList',
+  'print'
+];
+
+var actions1 = [
+  'PreviewMode',
+  'DisplaySetting',
+  'Watermark',
+  'Security',
+  'Thumbnail',
+  'BOTA',
+  'SnipMode',
+  'ExitSnipMode'
 ];
 
 Color randomColor() {

+ 167 - 0
example/lib/cpdf_reader_widget_security_example.dart

@@ -0,0 +1,167 @@
+// Copyright © 2014-2024 PDF Technologies, Inc. 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 ComPDFKit LICENSE AGREEMENT.
+// UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
+// This notice may not be removed from this file.
+
+import 'dart:io';
+import 'dart:math';
+
+import 'package:compdfkit_flutter/compdfkit.dart';
+import 'package:compdfkit_flutter/configuration/cpdf_configuration.dart';
+import 'package:compdfkit_flutter/configuration/cpdf_options.dart';
+import 'package:compdfkit_flutter/document/cpdf_watermark.dart';
+import 'package:compdfkit_flutter/util/extension/cpdf_color_extension.dart';
+import 'package:compdfkit_flutter/widgets/cpdf_reader_widget.dart';
+import 'package:compdfkit_flutter/widgets/cpdf_reader_widget_controller.dart';
+import 'package:compdfkit_flutter_example/page/cpdf_reader_widget_switch_preview_mode_page.dart';
+import 'package:compdfkit_flutter_example/utils/file_util.dart';
+import 'package:file_picker/file_picker.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+
+class CPDFReaderWidgetSecurityExample extends StatefulWidget {
+  final String documentPath;
+
+  const CPDFReaderWidgetSecurityExample(
+      {super.key, required this.documentPath});
+
+  @override
+  State<CPDFReaderWidgetSecurityExample> createState() =>
+      _CPDFReaderWidgetSecurityExampleState();
+}
+
+class _CPDFReaderWidgetSecurityExampleState
+    extends State<CPDFReaderWidgetSecurityExample> {
+
+  CPDFReaderWidgetController? _controller;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+        resizeToAvoidBottomInset: false,
+        appBar: AppBar(
+          title: const Text('CPDFReaderWidget Example'),
+          leading: IconButton(
+              onPressed: () {
+                Navigator.pop(context);
+              },
+              icon: const Icon(Icons.arrow_back)),
+          actions: null == _controller ? null : _buildAppBarActions(context),
+        ),
+        body: CPDFReaderWidget(
+          document: widget.documentPath,
+          configuration: CPDFConfiguration(
+              toolbarConfig: const CPDFToolbarConfig(
+                  iosLeftBarAvailableActions: [CPDFToolbarAction.thumbnail])),
+          onCreated: (controller) {
+            setState(() {
+              _controller = controller;
+            });
+          },
+        ));
+  }
+
+  List<Widget> _buildAppBarActions(BuildContext context) {
+    return [
+      PopupMenuButton<String>(
+        onSelected: (value) {
+          handleClick(value, _controller!);
+        },
+        itemBuilder: (context) {
+          return actions.map((action) {
+            return PopupMenuItem(
+              value: action,
+              child: SizedBox(width: 200, child: Text(action)),
+            );
+          }).toList();
+        },
+      ),
+    ];
+  }
+
+  void handleClick(String value, CPDFReaderWidgetController controller) async {
+    switch (value) {
+      case 'Set Password':
+        bool setPasswordResult = await controller.document.setPassword(
+            userPassword: '1234',
+            ownerPassword: '12345',
+            allowsPrinting: false,
+            allowsCopying: false,
+            encryptAlgo: CPDFDocumentEncryptAlgo.aes256);
+        debugPrint('ComPDFKit:set_user_password:$setPasswordResult');
+        break;
+      case 'Remove Password':
+        bool removePasswordResult = await controller.document.removePassword();
+        debugPrint('ComPDFKit:remove_user_password:$removePasswordResult');
+        break;
+      case 'Check Owner Password':
+        bool result = await controller.document.checkOwnerPassword('12345');
+        debugPrint('ComPDFKit:check_owner_password:$result');
+        break;
+      case 'Create Text Watermark':
+        await controller.document.createWatermark(CPDFWatermark.text(
+            textContent: 'Flutter',
+            scale: 1.0,
+            fontSize: 50,
+            textColor: Colors.deepOrange,
+            pages: [0, 1, 2, 3]));
+        break;
+      case 'Create Image Watermark':
+        File imageFile = await extractAsset(context, 'images/logo.png');
+        await controller.document.createWatermark(CPDFWatermark.image(
+          imagePath: imageFile.path,
+          pages: [0, 1, 2, 3],
+          horizontalAlignment: CPDFWatermarkHorizontalAlignment.center,
+          verticalAlignment: CPDFWatermarkVerticalAlignment.center,
+        ));
+        break;
+      case 'Create Image Watermark Pick Image':
+        FilePickerResult? pickerResult = await FilePicker.platform
+            .pickFiles(type: FileType.image, allowMultiple: false);
+        if (pickerResult != null) {
+          debugPrint('ComPDFKit:Document:${pickerResult.files.first.path}');
+          await controller.document.createWatermark(CPDFWatermark.image(
+            imagePath: pickerResult.files.first.path!,
+            pages: [0, 1, 2, 3],
+            scale: 0.5,
+            horizontalSpacing: 50,
+            verticalSpacing: 50,
+            opacity: 0.5,
+            horizontalAlignment: CPDFWatermarkHorizontalAlignment.center,
+            verticalAlignment: CPDFWatermarkVerticalAlignment.center,
+          ));
+          return;
+        }
+        break;
+      case 'Remove All Watermarks':
+        await controller.document.removeAllWatermarks();
+        break;
+      case 'Document Info':
+        var document = controller.document;
+        debugPrint(
+            'ComPDFKit:Document: fileName:${await document.getFileName()}');
+        debugPrint(
+            'ComPDFKit:Document: checkOwnerUnlocked:${await document.checkOwnerUnlocked()}');
+        debugPrint(
+            'ComPDFKit:Document: isEncrypted:${await document.isEncrypted()}');
+        debugPrint(
+            'ComPDFKit:Document: getPermissions:${await document.getPermissions()}');
+        debugPrint(
+            'ComPDFKit:Document: getEncryptAlgorithm:${await document.getEncryptAlgo()}');
+        break;
+    }
+  }
+}
+
+var actions = [
+  'Set Password',
+  'Remove Password',
+  'Check Owner Password',
+  'Create Text Watermark',
+  'Create Image Watermark',
+  'Create Image Watermark Pick Image',
+  'Remove All Watermarks',
+  'Document Info'
+];

+ 8 - 0
example/lib/examples.dart

@@ -15,6 +15,7 @@ import 'package:compdfkit_flutter/compdfkit.dart';
 import 'package:compdfkit_flutter/configuration/cpdf_configuration.dart';
 import 'package:compdfkit_flutter_example/cpdf_reader_widget_controller_example.dart';
 import 'package:compdfkit_flutter_example/cpdf_reader_widget_dark_theme_example.dart';
+import 'package:compdfkit_flutter_example/cpdf_reader_widget_security_example.dart';
 import 'package:compdfkit_flutter_example/utils/file_util.dart';
 import 'package:flutter/material.dart';
 import 'cpdf_reader_widget_example.dart';
@@ -49,6 +50,13 @@ List<Widget> examples(BuildContext context) =>
       FeatureItem(title: 'Widget Controller Examples',
           description: 'CPDFReaderWidget Controller fun example',
           onTap: () => showCPDFReaderWidgetTest(context)),
+      FeatureItem(title: 'Security feature Examples',
+          description: 'This example shows how to set passwords, watermarks, etc.',
+          onTap: () async {
+            File document = await extractAsset(
+                context, _documentPath, shouldOverwrite: false);
+            goTo(CPDFReaderWidgetSecurityExample(documentPath: document.path), context);
+          }),
       FeatureItem(
           title: 'Select External Files',
           description: 'Select pdf document from system file manager',

+ 72 - 0
example/lib/page/cpdf_reader_widget_display_setting_page.dart

@@ -0,0 +1,72 @@
+/*
+ * Copyright © 2014-2024 PDF Technologies, Inc. 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 ComPDFKit LICENSE AGREEMENT.
+ * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
+ * This notice may not be removed from this file.
+ *
+ */
+
+import 'package:compdfkit_flutter/widgets/cpdf_reader_widget_controller.dart';
+import 'package:flutter/material.dart';
+
+class CpdfReaderWidgetDisplaySettingPage extends StatefulWidget {
+  final CPDFReaderWidgetController controller;
+
+  const CpdfReaderWidgetDisplaySettingPage(
+      {super.key, required this.controller});
+
+  @override
+  State<CpdfReaderWidgetDisplaySettingPage> createState() =>
+      _CpdfReaderWidgetDisplaySettingPageState();
+}
+
+class _CpdfReaderWidgetDisplaySettingPageState
+    extends State<CpdfReaderWidgetDisplaySettingPage> {
+
+  bool _isVertical = true;
+  @override
+  void initState() {
+    super.initState();
+    initReaderViewWidgetStates();
+  }
+
+  void initReaderViewWidgetStates() async {
+    CPDFReaderWidgetController controller = widget.controller;
+    bool isVer = await controller.isVerticalMode();
+    setState(() async {
+      _isVertical = isVer;
+    });
+  }
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+        appBar: AppBar(
+          title: const Text('View Settings'),
+        ),
+        body: Column(
+          children: [_dividerLine('scroll'),
+          _scrollItem()],
+        ));
+  }
+
+  Widget _dividerLine(String title) {
+    return Container(
+      padding: const EdgeInsets.only(left: 8),
+      width: double.infinity,
+      height: 24,
+      color: Theme.of(context).primaryColor,
+      child: Text(title),
+    );
+  }
+
+  Widget _scrollItem() {
+    var textStyle =  Theme.of(context).textTheme.bodyMedium;
+    return Column(children: [
+      ListTile(title: Text('Vertical Scrolling', style: textStyle), trailing: this._isVertical ? const Icon(Icons.check) : null,),
+      ListTile(title: Text('Horizontal Scrolling', style: textStyle,),trailing: !this._isVertical ? const Icon(Icons.check) : null,),
+    ],);
+  }
+
+}

+ 67 - 0
example/lib/page/cpdf_reader_widget_switch_preview_mode_page.dart

@@ -0,0 +1,67 @@
+/*
+ * Copyright © 2014-2024 PDF Technologies, Inc. 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 ComPDFKit LICENSE AGREEMENT.
+ * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
+ * This notice may not be removed from this file.
+ *
+ */
+import 'package:compdfkit_flutter/configuration/cpdf_options.dart';
+import 'package:flutter/material.dart';
+
+class CpdfReaderWidgetSwitchPreviewModePage extends StatefulWidget {
+  final CPDFViewMode viewMode;
+
+  const CpdfReaderWidgetSwitchPreviewModePage(
+      {super.key, required this.viewMode});
+
+  @override
+  State<CpdfReaderWidgetSwitchPreviewModePage> createState() =>
+      _CpdfReaderWidgetSwitchPreviewModePageState();
+}
+
+class _CpdfReaderWidgetSwitchPreviewModePageState
+    extends State<CpdfReaderWidgetSwitchPreviewModePage> {
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    var textStyle = Theme.of(context).textTheme.labelLarge;
+    return SizedBox(
+        height: 336,
+        width: double.infinity,
+        child: Column(
+          children: [
+            SizedBox(
+              width: double.infinity,
+              height: 56,
+              child: Stack(
+                alignment: Alignment.center,
+                children: [
+                  Text('Mode', style: Theme.of(context).textTheme.titleMedium),
+                  const Positioned(right: 16, child: Icon(Icons.close))
+                ],
+              ),
+            ),
+            Expanded(
+                child: ListView.builder(
+                    itemCount: CPDFViewMode.values.length,
+                    itemBuilder: (context, index) {
+                      CPDFViewMode mode = CPDFViewMode.values[index];
+                      return ListTile(
+                          onTap: () async {
+                            Navigator.pop(context, mode);
+                          },
+                          title: Text(mode.name, style: textStyle),
+                          trailing: widget.viewMode == mode
+                              ? const Icon(Icons.check)
+                              : null);
+                    }))
+          ],
+        ));
+  }
+}

+ 1 - 2
example/lib/theme/themes.dart

@@ -12,8 +12,7 @@ final ThemeData lightTheme = ThemeData(
     useMaterial3: true,
     brightness: Brightness.light,
     colorScheme: const ColorScheme.light(
-        primary: Color(0xFFFAFCFF),
-        surface: Color(0xFFFAFCFF),
+        primary: Colors.blue,
         onPrimary: Color(0xFF43474D),
         onSecondary: Color(0xFF666666)),
     textTheme: const TextTheme(

+ 1 - 1
example/pubspec.lock

@@ -55,7 +55,7 @@ packages:
       path: ".."
       relative: true
     source: path
-    version: "2.2.0"
+    version: "2.2.1"
   cross_file:
     dependency: transitive
     description:

+ 1 - 1
example/pubspec.yaml

@@ -1,6 +1,6 @@
 name: compdfkit_flutter_example
 description: Demonstrates how to use the compdfkit_flutter plugin.
-version: 2.2.0
+version: 2.2.1
 homepage: https://www.compdf.com
 repository: https://github.com/ComPDFKit/compdfkit-pdf-sdk-flutter
 issue_tracker: https://www.compdf.com/support

+ 118 - 0
ios/Classes/CPDFConstants.swift

@@ -0,0 +1,118 @@
+//
+//  CPDFDocumentPlugin.swift
+//  compdfkit_flutter
+//  Copyright © 2014-2023 PDF Technologies, Inc. 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 ComPDFKit LICENSE AGREEMENT.
+//  UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
+//  This notice may not be removed from this file.
+
+import Foundation
+
+class CPDFConstants {
+    
+    static let openDocument = "open_document"
+    
+    static let getFileName = "get_file_name"
+    
+    static let isEncrypted = "is_encrypted"
+    
+    static let isImageDoc = "is_image_doc"
+    
+    static let getPermissions = "get_permissions"
+    
+    static let checkOwnerUnlocked = "check_owner_unlocked"
+    
+    static let checkOwnerPassword = "check_owner_password"
+    
+    static let hasChange = "has_change"
+
+    static let importAnnotations = "import_annotations"
+    
+    static let exportAnnotations = "export_annotations"
+    
+    static let removeAllAnnotations = "remove_all_annotations"
+    
+    static let getPageCount = "get_page_count"
+    
+    static let save = "save"
+    
+    static let setScale = "set_scale"
+    
+    static let getScale = "getScale"
+    
+    static let setReadBackgroundColor = "set_read_background_color"
+    
+    static let getReadBackgroundColor = "get_read_background_color"
+    
+    static let setFromFieldHighlight = "set_form_field_highlight"
+    
+    static let isFromFieldHighlight = "is_form_field_highlight"
+    
+    static let setLinkHighlight = "set_link_highlight"
+    
+    static let isLinkHighlight = "is_link_highlight"
+    
+    static let setVerticalMode = "set_vertical_mode"
+
+    static let isVerticalMode = "is_vertical_mode"
+    
+    static let setPageSpacing = "set_page_spacing"
+    
+    static let setMargin = "set_margin"
+    
+    static let setContinueMode = "set_continue_mode"
+    
+    static let isContinueMode = "is_continue_mode"
+    
+    static let setDoublePageMode = "set_double_page_mode"
+    
+    static let isDoublePageMode = "is_double_page_mode"
+    
+    static let setCoverPageMode = "set_cover_page_mode"
+    
+    static let isCoverPageMode = "is_cover_page_mode"
+    
+    static let setCropMode = "set_crop_mode"
+    
+    static let isCropMode = "is_crop_mode"
+    
+    static let setDisplayPageIndex = "set_display_page_index"
+    
+    static let getCurrentPageIndex = "get_current_page_index"
+    
+    static let set_preview_mode = "set_preview_mode"
+    
+    static let get_preview_mode = "get_preview_mode"
+    
+    static let showThumbnailView = "show_thumbnail_view"
+    
+    static let showBotaView = "show_bota_view"
+    
+    static let showAddWatermarkView = "show_add_watermark_view"
+    
+    static let showSecurityView = "show_security_view"
+    
+    static let showDisplaySettingsView = "show_display_settings_view"
+    
+    static let enterSnipMode = "enter_snip_mode"
+    
+    static let exitSnipMode = "exit_snip_mode"
+    
+    static let saveAs = "save_as"
+    
+    static let print = "print"
+    
+    static let removePassword = "remove_password"
+    
+    static let setPassword = "set_password"
+    
+    static let getEncryptAlgorithm = "get_encrypt_algorithm"
+    
+    static let createWatermark = "create_watermark"
+    
+    static let removeAllWatermark = "remove_all_watermark"
+    
+    
+}

+ 138 - 17
ios/Classes/reader/CPDFDocumentPlugin.swift

@@ -47,7 +47,32 @@ public class CPDFDocumentPlugin {
                 self.document = pdfListView.document
             }
             switch call.method {
-            case "open_document":
+            case CPDFConstants.save:
+                // save pdf
+                guard let pdfListView = self.pdfViewController!.pdfListView else {
+                    result(true)
+                    return
+                }
+                var isSuccess = false
+                if (pdfListView.isEditing() == true && pdfListView.isEdited() == true) {
+                    pdfListView.commitEditing()
+                    if pdfListView.document.isModified() == true {
+                        isSuccess = pdfListView.document.write(to: pdfListView.document.documentURL)
+                    }
+                    
+                } else {
+                    if(pdfListView.document != nil) {
+                        if pdfListView.document.isModified() == true {
+                            isSuccess = pdfListView.document.write(to: pdfListView.document.documentURL)
+                        }
+                    }
+                }
+                
+                if isSuccess {
+                    self._methodChannel.invokeMethod("saveDocument", arguments: nil)
+                }
+                result(isSuccess) // or return false
+            case CPDFConstants.openDocument:
                 let initInfo = call.arguments as? [String: Any]
                 let path = initInfo?["filePath"] as? String ?? ""
                 let password = initInfo?["password"] ?? ""
@@ -58,9 +83,8 @@ public class CPDFDocumentPlugin {
                 }
                 self.pdfViewController?.pdfListView?.document = self.document
                 self.pdfViewController?.pdfListView?.setNeedsDisplay()
-                result(2)
-            case "get_file_name":
-                
+                result(true)
+            case CPDFConstants.getFileName:
                 if(self.document == nil){
                     print("self.document is nil")
                     result("is nil")
@@ -68,13 +92,13 @@ public class CPDFDocumentPlugin {
                 }
                 let fileName = self.document?.documentURL.lastPathComponent
                 result(fileName)
-            case "is_encrypted":
+            case CPDFConstants.isEncrypted:
                 let isEncrypted = self.document?.isEncrypted ?? false
                 result(isEncrypted)
-            case "is_image_doc":
+            case CPDFConstants.isImageDoc:
                 let isImageDoc = self.document?.isImageDocument() ?? false
                 result(isImageDoc)
-            case "get_permissions":
+            case CPDFConstants.getPermissions:
                 let permissions = self.document?.permissionsStatus ?? .none
                 switch permissions {
                 case .none:
@@ -83,31 +107,28 @@ public class CPDFDocumentPlugin {
                     result(1)
                 case .owner:
                     result(2)
-              
                 default:
                     result(0)
                 }
-                
-            case "check_owner_unlocked":
+            case CPDFConstants.checkOwnerUnlocked:
                 let owner = self.document?.isCheckOwnerUnlocked() ?? false
-                
                 result(owner)
-            case "check_password":
+            case CPDFConstants.checkOwnerPassword:
                 let info = call.arguments as? [String: Any]
                 let password = info?["password"] as? String ?? ""
                 let isOwnerPassword = self.document?.checkOwnerPassword(password) ?? false
                 result(isOwnerPassword)
-            case "has_change":
+            case CPDFConstants.hasChange:
                 let isModified = self.document?.isModified() ?? false
                 result(isModified)
-            case "import_annotations":
+            case CPDFConstants.importAnnotations:
                 let importPath = call.arguments as? String ?? ""
                 let success = self.document?.importAnnotation(fromXFDFPath: importPath) ?? false
                 if success {
                     self.pdfViewController?.pdfListView?.setNeedsDisplayForVisiblePages()
                 }
                 result(success)
-            case "export_annotations":
+            case CPDFConstants.exportAnnotations:
                 let fileNameWithExtension = self.document?.documentURL.lastPathComponent ?? ""
                 let fileName = (fileNameWithExtension as NSString).deletingPathExtension
                 let documentFolder = NSHomeDirectory().appending("/Documents/\(fileName)_xfdf.xfdf")
@@ -117,7 +138,7 @@ public class CPDFDocumentPlugin {
                 } else {
                     result("")
                 }
-            case "remove_all_annotations":
+            case CPDFConstants.removeAllAnnotations:
                 let pageCount = self.document?.pageCount ?? 0
                 for i in 0..<pageCount {
                     let page = self.document?.page(at: i)
@@ -126,13 +147,113 @@ public class CPDFDocumentPlugin {
                 self.pdfViewController?.pdfListView?.setNeedsDisplayForVisiblePages()
                 self.pdfViewController?.pdfListView?.updateActiveAnnotations([])
                 result(true)
-            case "get_page_count":
+            case CPDFConstants.getPageCount:
                 let count = self.document?.pageCount ?? 1
                 result(count)
+            case CPDFConstants.saveAs:
+                // TODO: 另存为, 将文档保存到客户指定位置
+                let info = call.arguments as? [String: Any]
+                // 保存路径, 包含了文件名称,完整的路径
+                let savePath = self.getValue(from: info, key: "save_path", defaultValue: "")
+                // 是否移除安全设置
+                let removeSecurity = self.getValue(from: info, key: "remove_security", defaultValue: false)
+                // 是否保存时包含字体集
+                let fontSubSet = self.getValue(from: info, key: "font_sub_set", defaultValue: true)
+                // 返回值:true:保存成功, false:保存失败
+                // result(true | false)
+                break;
+            case CPDFConstants.print:
+                // TODO: 直接唤起进入系统的打印功能界面
+                // 返回值:无
+                break;
+            case CPDFConstants.removePassword:
+                // TODO: 移除文档中的密码并保存
+                // 返回值:true:成功、false:失败
+                // result(true | false)
+                break;
+            case CPDFConstants.setPassword:
+                // TODO: 设置密码,包含:用户密码、所有者密码、允许复制,打印权限、加密方式
+                // 设置完后,增量保存到当前文档
+                let info = call.arguments as? [String: Any]
+                let userPassword : String = self.getValue(from: info, key: "user_password", defaultValue: "")
+                let ownerPassword : String = self.getValue(from: info, key: "owner_password", defaultValue: "")
+                let allowsPrinting : Bool = self.getValue(from: info, key: "allows_printing", defaultValue: true)
+                let allowsCopying : Bool = self.getValue(from: info, key: "allows_copying", defaultValue: true)
+                // 加密方式,传入的类型为:rc4, aes128, aes256, noEncryptAlgo
+                let encryptAlgo : String = self.getValue(from: info, key: "encrypt_algo", defaultValue: "rc4")
+                // 返回值:true: 设置成功, false:设置失败
+                // result(true | false)
+                break;
+            case CPDFConstants.getEncryptAlgorithm:
+                // TODO: 返回当前文档的加密方式
+                // 返回值:rc4, aes128, aes256, noEncryptAlgo
+                // result("rc4" | "aes128" | "aes256" | "noEncryptAlgo")
+                
+                break;
+            case CPDFConstants.createWatermark:
+                // TODO: 添加水印到当前文档
+                // 返回值:无
+                self.createWatermark(call: call, result: result)
+                break;
+            case CPDFConstants.removeAllWatermark:
+                // TODO: 移除所有水印
+                // 返回值:无
+                break;
             default:
                 result(FlutterMethodNotImplemented)
             }
         });
+    }
+    
+    private func createWatermark(call: FlutterMethodCall, result: FlutterResult) {
+        let info = call.arguments as? [String: Any]
+        // 水印类型:text, image
+        let type : String = self.getValue(from: info, key: "type", defaultValue: "")
+        // 页码:"0,1,2,3,4"
+        let pages : String = self.getValue(from: info, key: "pages", defaultValue: "")
+        // 文本水印的水印文字
+        let textContent : String = self.getValue(from: info, key: "text_content", defaultValue: "")
+        // 图片水印的图片路径
+        let imagePath : String = self.getValue(from: info, key: "image_path", defaultValue: "")
+        let textColor : String = self.getValue(from: info, key: "text_color", defaultValue: "#000000")
+        let fontSize : Int = self.getValue(from: info, key: "font_size", defaultValue: 24)
+        let scale : Double = self.getValue(from: info, key: "scale", defaultValue: 1.0)
+        let rotation : Double = self.getValue(from: info, key: "rotation", defaultValue: 45)
+        let opacity : Double = self.getValue(from: info, key: "opacity", defaultValue: 1.0)
+        // 垂直对齐方式:top, center, bottom
+        let verticalAlignment : String = self.getValue(from: info, key: "vertical_alignment", defaultValue: "center")
+        // 水平对齐方式:left, center, right
+        let horizontalAlignment : String = self.getValue(from: info, key: "horizontal_alignment", defaultValue: "center")
+        let verticalOffset : Double = self.getValue(from: info, key: "vertical_offset", defaultValue: 0)
+        let horizontalOffset : Double = self.getValue(from: info, key: "horizontal_offset", defaultValue: 0)
+        let isFront : Bool = self.getValue(from: info, key: "is_front", defaultValue: true)
+        let isTilePage : Bool = self.getValue(from: info, key: "is_tile_page", defaultValue: false)
+        let horizontalSpacing : Double = self.getValue(from: info, key: "horizontal_spacing", defaultValue: 0)
+        let verticalSpacing : Double = self.getValue(from: info, key: "vertical_spacing", defaultValue: 0)
         
+        if(pages.isEmpty){
+            result(FlutterError(code: "WATERMARK_FAIL", message: "The page range cannot be empty, please set the page range, for example: pages: \"0,1,2,3\"", details: nil))
+            return
+        }
+        
+        if("text" == type){
+            if(textContent.isEmpty){
+                result(FlutterError(code: "WATERMARK_FAIL", message: "Add text watermark, the text cannot be empty", details: nil))
+                return
+            }
+        } else{
+            // 添加图片水印
+            if(imagePath.isEmpty){
+                result(FlutterError(code: "WATERMARK_FAIL", message: "image path is empty", details: nil))
+                return
+            }
+        }
+    }
+    
+    func getValue<T>(from info: [String: Any]?, key: String, defaultValue: T) -> T {
+        guard let value = info?[key] as? T else {
+            return defaultValue
+        }
+        return value
     }
 }

+ 66 - 47
ios/Classes/reader/CPDFViewCtrlPlugin.swift

@@ -35,44 +35,19 @@ class CPDFViewCtrlPlugin {
             print("ComPDFKit-Flutter: iOS-MethodChannel: [method:\(call.method)]")
               // Handle battery messages.
             switch call.method {
-            case "save":
-                // save pdf
-                guard let pdfListView = self.pdfViewController.pdfListView else {
-                    result(true)
-                    return
-                }
-                var isSuccess = false
-                if (pdfListView.isEditing() == true && pdfListView.isEdited() == true) {
-                    pdfListView.commitEditing()
-                    if pdfListView.document.isModified() == true {
-                        isSuccess = pdfListView.document.write(to: pdfListView.document.documentURL)
-                    }
-                    
-                } else {
-                    if(pdfListView.document != nil) {
-                        if pdfListView.document.isModified() == true {
-                            isSuccess = pdfListView.document.write(to: pdfListView.document.documentURL)
-                        }
-                    }
-                }
-                
-                if isSuccess {
-                    self._methodChannel.invokeMethod("saveDocument", arguments: nil)
-                }
-                result(isSuccess) // or return false
-            case "set_scale":
+            case CPDFConstants.setScale:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
                 let scaleValue = call.arguments as! NSNumber
                 pdfListView.setScaleFactor(CGFloat(truncating: scaleValue), animated: true)
-            case "get_scale":
+            case CPDFConstants.getScale:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result(1.0)
                     return
                 }
                 result(pdfListView.scaleFactor)
-            case "set_read_background_color":
+            case CPDFConstants.setReadBackgroundColor:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
@@ -94,7 +69,7 @@ class CPDFViewCtrlPlugin {
                 }
 
                 pdfListView.layoutDocumentView()
-            case "get_read_background_color":
+            case CPDFConstants.getReadBackgroundColor:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result("#FFFFFF")
                     return
@@ -116,40 +91,40 @@ class CPDFViewCtrlPlugin {
                     result("#FFFFFF")
                 }
                
-            case "set_form_field_highlight":
+            case CPDFConstants.setFromFieldHighlight:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
                 let highlightForm = call.arguments as! Bool
                 CPDFKitConfig.sharedInstance().setEnableFormFieldHighlight(highlightForm)
                 pdfListView.layoutDocumentView()
-            case "is_form_field_highlight":
+            case CPDFConstants.isFromFieldHighlight:
                 result(CPDFKitConfig.sharedInstance().enableFormFieldHighlight())
-            case "set_link_highlight":
+            case CPDFConstants.setLinkHighlight:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
                 let linkHighlight = call.arguments as! Bool
                 CPDFKitConfig.sharedInstance().setEnableLinkFieldHighlight(linkHighlight)
                 pdfListView.layoutDocumentView()
-            case "is_link_highlight":
+            case CPDFConstants.isLinkHighlight:
                 result(CPDFKitConfig.sharedInstance().enableLinkFieldHighlight())
-            case "set_vertical_mode":
+            case CPDFConstants.setVerticalMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
                 let verticalMode = call.arguments as! Bool
                 pdfListView.displayDirection = verticalMode ? .vertical : .horizontal
                 pdfListView.layoutDocumentView()
-            case "is_vertical_mode":
+            case CPDFConstants.isVerticalMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result(true)
                     return
                 }
                 result(pdfListView.displayDirection == .vertical)
-            case "set_page_spacing":
+            case CPDFConstants.setPageSpacing:
                 result(FlutterError(code: "NOT_SUPPORT", message: "This method is not supported on iOS. Please use controller.setMargins(left,top,right,bottom)", details: ""))
-            case "set_margin":
+            case CPDFConstants.setMargin:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
@@ -162,20 +137,20 @@ class CPDFViewCtrlPlugin {
                     right: CGFloat(truncating: (spacingInfo["right"] ?? 10))
                     )
                 pdfListView.layoutDocumentView()
-            case "set_continue_mode":
+            case CPDFConstants.setContinueMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
                 let continueMode = call.arguments as! Bool
                 pdfListView.displaysPageBreaks = continueMode
                 pdfListView.layoutDocumentView()
-            case "is_continue_mode":
+            case CPDFConstants.isContinueMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result(true)
                     return
                 }
                 result(pdfListView.displaysPageBreaks)
-            case "set_double_page_mode":
+            case CPDFConstants.setDoublePageMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
@@ -183,13 +158,13 @@ class CPDFViewCtrlPlugin {
                 pdfListView.displayTwoUp = twoUp
                 pdfListView.displaysAsBook = false
                 pdfListView.layoutDocumentView()
-            case "is_double_page_mode":
+            case CPDFConstants.isDoublePageMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result(false)
                     return
                 }
                 result(pdfListView.displayTwoUp)
-            case "set_cover_page_mode":
+            case CPDFConstants.setCoverPageMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
@@ -197,26 +172,26 @@ class CPDFViewCtrlPlugin {
                 pdfListView.displaysAsBook = coverPageMode
                 pdfListView.displayTwoUp = coverPageMode
                 pdfListView.layoutDocumentView()
-            case "is_cover_page_mode":
+            case CPDFConstants.isCoverPageMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result(false)
                     return
                 }
                 result(pdfListView.displaysAsBook)
-            case "set_crop_mode":
+            case CPDFConstants.setCropMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
                 let cropMode = call.arguments as! Bool
                 pdfListView.displayCrop = cropMode
                 pdfListView.layoutDocumentView()
-            case "is_crop_mode":
+            case CPDFConstants.isCropMode:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result(false)
                     return
                 }
                 result(pdfListView.displayCrop)
-            case "set_display_page_index":
+            case CPDFConstants.setDisplayPageIndex:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     return
                 }
@@ -224,12 +199,56 @@ class CPDFViewCtrlPlugin {
                 let pageIndex = info["pageIndex"] as! NSNumber
                 let animated = info["animated"] as! Bool
                 pdfListView.go(toPageIndex: Int(truncating: pageIndex), animated: animated)
-            case "get_current_page_index":
+            case CPDFConstants.getCurrentPageIndex:
                 guard let pdfListView = self.pdfViewController.pdfListView else {
                     result(0)
                     return
                 }
                 result(pdfListView.currentPageIndex)
+            case CPDFConstants.set_preview_mode:
+                // TODO: 设置预览模式
+                let mode = call.arguments as! String
+                // viewer: 预览模式
+                // annotations : 注释模式
+                // contentEditor : 内容编辑模式
+                // forms : 表单模式
+                // signatures : 签名模式
+                // 返回值 : 无
+            case CPDFConstants.get_preview_mode:
+                // TODO: 获取当前的预览模式
+                // 返回值:viewer, annotations, contentEditor, forms, signatures
+                result("viewer")
+            case CPDFConstants.showThumbnailView:
+                // TODO: 现实缩略图列表弹窗界面
+                // 是否编辑模式
+                // true : 要直接进入到页面编辑模式
+                // false : 展示缩略图状态即可
+                // 返回值: 无
+                let editMode = call.arguments as! Bool
+            case CPDFConstants.showBotaView:
+                // TODO: 显示BOTA 界面
+                // 返回值:无
+                break;
+            case CPDFConstants.showAddWatermarkView:
+                // TODO: 显示添加水印界面
+                // 返回值:无
+                break;
+            case CPDFConstants.showSecurityView:
+                // TODO: 显示安全设置界面
+                // 返回值:无
+                break;
+            case CPDFConstants.showDisplaySettingsView:
+                // TODO: 显示预览设置界面
+                // 返回值:无
+                break;
+            case CPDFConstants.enterSnipMode:
+                // TODO: 进入截取模式
+                // 返回值:无
+                break;
+            case CPDFConstants.exitSnipMode:
+                // TODO: 退出截取模式
+                // 返回值:无
+                break;
             default:
                 result(FlutterMethodNotImplemented)
             }

+ 25 - 0
lib/compdfkit.dart

@@ -101,4 +101,29 @@ class ComPDFKit {
     final String? filePath = await _methodChannel.invokeMethod('pick_file');
     return filePath;
   }
+
+  /// This method is supported only on the Android platform. It is used to create a URI for saving a file on the Android device.
+  /// The file is saved in the `Downloads` directory by default, but you can specify a subdirectory within `Downloads` using the
+  /// [childDirectoryName] parameter. If the [childDirectoryName] is not provided, the file will be saved directly in the `Downloads` directory.
+  /// The [fileName] parameter is required to specify the name of the file (e.g., `test.pdf`).
+  ///
+  /// Example usage:
+  /// ```dart
+  /// String? uri = await ComPDFKit.createUri('test.pdf');
+  /// ```
+  ///
+  /// - [fileName] (required) specifies the name of the file, for example `test.pdf`.
+  /// - [childDirectoryName] (optional) specifies a subdirectory within the `Downloads` folder.
+  /// - [mimeType] (optional) is the MIME type of the file, defaulting to `application/pdf`.
+  static Future<String?> createUri(
+      String fileName,
+      {String? childDirectoryName,
+        String mimeType = 'application/pdf'}) async {
+    final String? uri = await _methodChannel.invokeMethod('create_uri', {
+      'file_name' : fileName,
+      'child_directory_name' : childDirectoryName,
+      'mime_type' : mimeType
+    });
+    return uri;
+  }
 }

+ 20 - 0
lib/configuration/cpdf_options.dart

@@ -215,4 +215,24 @@ enum CPDFDocumentError {
   /// Error page.
   errorPage
 
+}
+
+enum CPDFDocumentEncryptAlgo {
+  // RC4 encryption algorithm.
+  rc4,
+
+  /// AES encryption with a 128-bit key.
+  aes128,
+
+  /// AES encryption with a 256-bit key.
+  aes256,
+
+  /// No encryption algorithm selected.
+  noEncryptAlgo;
+}
+
+enum CPDFWatermarkType {
+  text,
+
+  image
 }

+ 154 - 1
lib/document/cpdf_document.dart

@@ -6,6 +6,7 @@
 // This notice may not be removed from this file.
 
 import 'package:compdfkit_flutter/configuration/cpdf_options.dart';
+import 'package:compdfkit_flutter/document/cpdf_watermark.dart';
 import 'package:flutter/services.dart';
 
 /// A class to handle PDF documents without using [CPDFReaderWidget]
@@ -168,6 +169,158 @@ class CPDFDocument {
     return await _channel.invokeMethod('get_page_count');
   }
 
-  // Future<void> getInfo() async {}
+  /// Save document
+  ///
+  /// example:
+  /// ```dart
+  /// bool result = await _controller.save();
+  /// ```
+  /// Return value: **true** if the save is successful,
+  /// **false** if the save fails.
+  Future<bool> save() async {
+    return await _channel.invokeMethod('save');
+  }
+
+  /// Saves the document to the specified directory.
+  ///
+  /// Example usage:
+  /// ```dart
+  /// await controller.document.saveAs('data/your_package_name/files/xxx.pdf')
+  /// ```
+  /// - [savePath] Specifies the path where the document should be saved.
+  /// On Android, both file paths and URIs are supported. For example:
+  ///   - File path: `/data/user/0/com.compdfkit.flutter.example/cache/temp/PDF_Document.pdf`
+  ///   - URI: `content://media/external/file/1000045118`
+  /// - [removeSecurity] Whether to remove the document's password.
+  /// - [fontSubSet] Whether to embed the font subset when saving the PDF.
+  /// This will affect how text appears in other PDF software. This is a time-consuming operation.
+  Future<bool> saveAs(String savePath, {
+    bool removeSecurity = false,
+    bool fontSubSet = true
+  }) async {
+    try {
+      return await _channel.invokeMethod('save_as', {
+        'save_path' : savePath,
+        'remove_security' : removeSecurity,
+        'font_sub_set' : fontSubSet
+      });
+    } on PlatformException catch (e) {
+      print(e.details);
+      return false;
+    } catch (e) {
+      return false;
+    }
+  }
+
+  /// Invokes the system's print service to print the current document.
+  ///
+  /// example:
+  /// ```dart
+  /// await document.printDocument();
+  /// ```
+  Future<void> printDocument() async {
+    await _channel.invokeMethod('print');
+  }
+
+  /// Remove the user password and owner permission password
+  /// set in the document, and perform an incremental save.
+  ///
+  /// example:
+  /// ```dart
+  /// bool result = await document.removePassword();
+  /// ```
+  Future<bool> removePassword() async{
+    try{
+      return await _channel.invokeMethod('remove_password');
+    } on PlatformException catch (e) {
+      print(e.message);
+      return false;
+    }
+  }
+
+  /// This method sets the document password, including the user password for access restrictions
+  /// and the owner password for granting permissions.
+  ///
+  /// - To enable permissions like printing or copying, the owner password must be set; otherwise,
+  ///   the settings will not take effect.
+  ///
+  /// example:
+  /// ```dart
+  /// bool result = controller.document.setPassword(
+  ///   userPassword : '1234',
+  ///   ownerPassword : '4321',
+  ///   allowsPrinting : false,
+  ///   allowsCopying : false,
+  ///   encryptAlgo = CPDFDocumentEncryptAlgo.rc4
+  /// );
+  /// ```
+  Future<bool> setPassword({
+    String? userPassword,
+    String? ownerPassword,
+    bool allowsPrinting = true,
+    bool allowsCopying = true,
+    CPDFDocumentEncryptAlgo encryptAlgo = CPDFDocumentEncryptAlgo.rc4
+  }) async {
+    try{
+      return await _channel.invokeMethod('set_password', {
+        'user_password' : userPassword,
+        'owner_password' : ownerPassword,
+        'allows_printing' : allowsPrinting,
+        'allows_copying' : allowsCopying,
+        'encrypt_algo' : encryptAlgo.name
+      });
+    } on PlatformException catch (e) {
+      print(e.message);
+      return false;
+    }
+  }
+
+  /// Get the encryption algorithm of the current document
+  ///
+  /// example:
+  /// ```dart
+  /// CPDFDocumentEncryptAlgo encryptAlgo = await controller.document.getEncryptAlgo();
+  /// ```
+  Future<CPDFDocumentEncryptAlgo> getEncryptAlgo() async {
+    String encryptAlgoStr = await _channel.invokeMethod('get_encrypt_algorithm');
+    return CPDFDocumentEncryptAlgo.values.where((e) => e.name == encryptAlgoStr).first;
+  }
+
+  /// Create document watermarks, including text watermarks and image watermarks
+  ///
+  /// - Add Text Watermark Example:
+  /// ```dart
+  /// await controller.document.createWatermark(CPDFWatermark.text(
+  ///             textContent: 'Flutter',
+  ///             scale: 1.0,
+  ///             fontSize: 50,
+  ///             textColor: Colors.deepOrange,
+  ///             pages: [0, 1, 2, 3,8,9]));
+  /// ```
+  ///
+  /// - Add Image Watermark Example:
+  /// ```dart
+  /// File imageFile = await extractAsset(context, 'images/logo.png');
+  /// await controller.document.createWatermark(CPDFWatermark.image(
+  ///           imagePath: imageFile.path,
+  ///           pages: [0, 1, 2, 3],
+  ///           horizontalSpacing: 50,
+  ///           verticalSpacing: 50,
+  ///           horizontalAlignment: CPDFWatermarkHorizontalAlignment.center,
+  ///           verticalAlignment: CPDFWatermarkVerticalAlignment.center,
+  ///         ));
+  /// ```
+  Future<bool> createWatermark(CPDFWatermark watermark) async {
+    return await _channel.invokeMethod('create_watermark', watermark.toJson());
+  }
 
+  /// remove all watermark
+  ///
+  /// example:
+  /// ```dart
+  /// await controller.document.removeAllWatermarks();
+  /// ```
+  Future<void> removeAllWatermarks() async{
+    return await _channel.invokeMethod('remove_all_watermarks');
+  }
 }

+ 213 - 0
lib/document/cpdf_watermark.dart

@@ -0,0 +1,213 @@
+/*
+ * Copyright © 2014-2024 PDF Technologies, Inc. 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 ComPDFKit LICENSE AGREEMENT.
+ * UNAUTHORIZED REPRODUCTION OR DISTRIBUTION IS SUBJECT TO CIVIL AND CRIMINAL PENALTIES.
+ * This notice may not be removed from this file.
+ *
+ */
+
+
+import 'package:compdfkit_flutter/configuration/cpdf_options.dart';
+import 'package:compdfkit_flutter/util/extension/cpdf_color_extension.dart';
+import 'package:flutter/material.dart';
+
+class CPDFWatermark {
+  /// Watermark type, can be either [CPDFWatermarkType.text] or [CPDFWatermarkType.image]
+  final CPDFWatermarkType type;
+
+  /// Text content for text-type watermark
+  final String textContent;
+
+  /// Image path for image-type watermark
+  final String imagePath;
+
+  final Color textColor;
+
+  final int fontSize;
+
+  /// Scaling factor, default is 1.0
+  final double scale;
+
+  /// Watermark rotation angle, default is 45°
+  final double rotation;
+
+  /// Watermark opacity, default is 1.0, range is 0.0 to 1.0
+  final double opacity;
+
+  /// Vertical alignment of the watermark, default is vertical center alignment
+  final CPDFWatermarkVerticalAlignment verticalAlignment;
+
+  /// Horizontal alignment of the watermark, default is center alignment
+  final CPDFWatermarkHorizontalAlignment horizontalAlignment;
+
+  /// Vertical offset for watermark position
+  final double verticalOffset;
+
+  /// Horizontal offset for watermark position
+  final double horizontalOffset;
+
+  /// Pages to add the watermark to, e.g., "1,2,3,4,5"
+  final List<int> pages;
+
+  /// Position the watermark in front of the content
+  final bool isFront;
+
+  /// Enable watermark tiling
+  final bool isTilePage;
+
+  /// Set the horizontal spacing for tiled watermarks
+  final double horizontalSpacing;
+
+  /// Set the vertical spacing for tiled watermarks
+  final double verticalSpacing;
+
+  CPDFWatermark(
+      {required this.type,
+      required this.pages,
+      this.textContent = "",
+      this.imagePath = "",
+        this.textColor = Colors.black,
+        this.fontSize = 24,
+      this.scale = 1.0,
+      this.rotation = 45,
+      this.opacity = 1,
+      this.verticalAlignment = CPDFWatermarkVerticalAlignment.center,
+      this.horizontalAlignment = CPDFWatermarkHorizontalAlignment.center,
+      this.verticalOffset = 0,
+      this.horizontalOffset = 0,
+      this.isFront = true,
+      this.isTilePage = false,
+      this.horizontalSpacing = 0,
+      this.verticalSpacing = 0});
+
+  /// Text watermark constructor
+  ///
+  /// This constructor creates a text watermark with customizable properties.
+  ///
+  /// - [textContent]: The text content of the watermark. (Required)
+  /// - [pages]: A list of page indices where the watermark should be applied, e.g., [0, 1, 2, 3] represents pages 1 through 4. (Required)
+  /// - [textColor]: The color of the watermark text. Default is `Colors.black`.
+  /// - [fontSize]: The font size of the watermark text. Default is `24`.
+  /// - [scale]: The scaling factor for the text. Default is `1.0`.
+  /// - [rotation]: The rotation angle of the watermark in degrees. Default is `45.0`.
+  /// - [opacity]: The transparency of the watermark, where `1.0` is fully opaque and `0.0` is fully transparent. Default is `1.0`.
+  /// - [verticalAlignment]: The vertical alignment of the watermark on the page. Default is `CPDFWatermarkVerticalAlignment.center`.
+  /// - [horizontalAlignment]: The horizontal alignment of the watermark on the page. Default is `CPDFWatermarkHorizontalAlignment.center`.
+  /// - [verticalOffset]: The vertical offset of the watermark relative to the alignment position. Default is `0.0`.
+  /// - [horizontalOffset]: The horizontal offset of the watermark relative to the alignment position. Default is `0.0`.
+  /// - [isFront]: Whether the watermark should appear in front of the page content. Default is `true`.
+  /// - [isTilePage]: Whether the watermark should be tiled across the page. Default is `false`.
+  /// - [horizontalSpacing]: The horizontal spacing between tiled watermarks. Default is `0.0`.
+  /// - [verticalSpacing]: The vertical spacing between tiled watermarks. Default is `0.0`.
+  CPDFWatermark.text(
+      {required String textContent,
+      required List<int> pages,
+        Color textColor = Colors.black,
+        int fontSize = 24,
+      double scale = 1.0,
+      double rotation = 45.0,
+      double opacity = 1.0,
+      CPDFWatermarkVerticalAlignment verticalAlignment =
+          CPDFWatermarkVerticalAlignment.center,
+      CPDFWatermarkHorizontalAlignment horizontalAlignment =
+          CPDFWatermarkHorizontalAlignment.center,
+      double verticalOffset = 0.0,
+      double horizontalOffset = 0.0,
+      bool isFront = true,
+      bool isTilePage = false,
+      double horizontalSpacing = 0.0,
+      double verticalSpacing = 0.0})
+      : this(
+            type: CPDFWatermarkType.text,
+            textContent: textContent,
+            textColor: textColor,
+            fontSize: fontSize,
+            pages: pages,
+            scale: scale,
+            rotation: rotation,
+            opacity: opacity,
+            verticalAlignment: verticalAlignment,
+            horizontalAlignment: horizontalAlignment,
+            verticalOffset: verticalOffset,
+            horizontalOffset: horizontalOffset,
+            isFront: isFront,
+            isTilePage: isTilePage,
+            horizontalSpacing: horizontalSpacing,
+            verticalSpacing: verticalSpacing);
+
+  /// Image watermark constructor
+  ///
+  /// This constructor creates an image watermark with customizable properties.
+  ///
+  /// - [imagePath]: The file path of the image to be used as the watermark. (Required)
+  /// - [pages]: A list of page indices where the watermark should be applied, e.g., [0, 1, 2, 3] represents pages 1 through 4. (Required)
+  /// - [scale]: The scaling factor for the image. Default is 1.0.
+  /// - [rotation]: The rotation angle of the watermark in degrees. Default is 45.0.
+  /// - [opacity]: The transparency of the watermark, where 1.0 is fully opaque and 0.0 is fully transparent. Default is 1.0.
+  /// - [verticalAlignment]: The vertical alignment of the watermark on the page. Default is `CPDFWatermarkVerticalAlignment.center`.
+  /// - [horizontalAlignment]: The horizontal alignment of the watermark on the page. Default is `CPDFWatermarkHorizontalAlignment.center`.
+  /// - [verticalOffset]: The vertical offset of the watermark relative to the alignment position. Default is 0.0.
+  /// - [horizontalOffset]: The horizontal offset of the watermark relative to the alignment position. Default is 0.0.
+  /// - [isFront]: Whether the watermark should appear in front of the page content. Default is `true`.
+  /// - [isTilePage]: Whether the watermark should be tiled across the page. Default is `false`.
+  /// - [horizontalSpacing]: The horizontal spacing between tiled watermarks. Default is 0.0.
+  /// - [verticalSpacing]: The vertical spacing between tiled watermarks. Default is 0.0.
+  CPDFWatermark.image({
+    required String imagePath,
+    required List<int> pages,
+    double scale = 1.0,
+    double rotation = 45.0,
+    double opacity = 1.0,
+    CPDFWatermarkVerticalAlignment verticalAlignment =
+        CPDFWatermarkVerticalAlignment.center,
+    CPDFWatermarkHorizontalAlignment horizontalAlignment =
+        CPDFWatermarkHorizontalAlignment.center,
+    double verticalOffset = 0.0,
+    double horizontalOffset = 0.0,
+    bool isFront = true,
+    bool isTilePage = false,
+    double horizontalSpacing = 0.0,
+    double verticalSpacing = 0.0,
+  }) : this(
+          type: CPDFWatermarkType.image,
+          imagePath: imagePath,
+          pages: pages,
+          scale: scale,
+          rotation: rotation,
+          opacity: opacity,
+          verticalAlignment: verticalAlignment,
+          horizontalAlignment: horizontalAlignment,
+          verticalOffset: verticalOffset,
+          horizontalOffset: horizontalOffset,
+          isFront: isFront,
+          isTilePage: isTilePage,
+          horizontalSpacing: horizontalSpacing,
+          verticalSpacing: verticalSpacing,
+        );
+
+  Map<String, dynamic> toJson() => {
+    'type': type.name,
+    'text_content': textContent,
+    'image_path' : imagePath,
+    'text_color' : textColor.toHex(),
+    'font_size' : fontSize,
+    'scale' : scale,
+    'rotation' : rotation,
+    'opacity' : opacity,
+    'vertical_alignment' : verticalAlignment.name,
+    'horizontal_alignment' : horizontalAlignment.name,
+    'vertical_offset' : verticalOffset,
+    'horizontal_offset' : horizontalOffset,
+    'pages' : pages.join(','),
+    'is_front' : isFront,
+    'is_tile_page' : isTilePage,
+    'horizontal_spacing' : horizontalSpacing,
+    'vertical_spacing' : verticalSpacing
+  };
+}
+
+enum CPDFWatermarkVerticalAlignment { top, center, bottom }
+
+enum CPDFWatermarkHorizontalAlignment { left, center, right }

+ 53 - 1
lib/widgets/cpdf_reader_widget_controller.dart

@@ -64,8 +64,9 @@ class CPDFReaderWidgetController {
   /// ```
   /// Return value: **true** if the save is successful,
   /// **false** if the save fails.
+  @Deprecated("use CPDFDocument().save()")
   Future<bool> save() async {
-    return await _channel.invokeMethod('save');
+    return await document.save();
   }
 
   /// Set the page scale
@@ -380,4 +381,55 @@ class CPDFReaderWidgetController {
   Future<bool> hasChange() async {
     return await _document.hasChange();
   }
+
+  /// Switch the mode displayed by the current CPDFReaderWidget.
+  /// Please see [CPDFViewMode] for available modes.
+  ///
+  /// example:
+  /// ```dart
+  /// await setPreviewMode(CPDFViewMode.viewer);
+  /// ```
+  Future<void> setPreviewMode(CPDFViewMode viewMode) async {
+    await _channel.invokeMethod('set_preview_mode', viewMode.name);
+  }
+
+  /// Get the currently displayed mode
+  ///
+  /// example:
+  /// ```dart
+  /// CPDFViewMode mode = await controller.getPreviewMode();
+  /// ```
+  Future<CPDFViewMode> getPreviewMode() async {
+    String modeName = await _channel.invokeMethod('get_preview_mode');
+    return CPDFViewMode.values.where((e) => e.name == modeName).first;
+  }
+
+  Future<void> showThumbnailView(bool editMode) async{
+    await _channel.invokeMethod('show_thumbnail_view', editMode);
+  }
+
+  Future<void> showBotaView() async{
+    await _channel.invokeMethod('show_bota_view');
+  }
+
+  Future<void> showAddWatermarkView() async{
+    await _channel.invokeMethod('show_add_watermark_view');
+  }
+
+  Future<void> showSecurityView() async {
+    await _channel.invokeMethod('show_security_view');
+  }
+
+  Future<void> showDisplaySettingView() async{
+    await _channel.invokeMethod('show_display_settings_view');
+  }
+
+  Future<void> enterSnipMode() async {
+    await _channel.invokeMethod('enter_snip_mode');
+  }
+
+  Future<void> exitSnipMode() async {
+    await _channel.invokeMethod('exit_snip_mode');
+  }
+
 }

+ 1 - 1
pubspec.yaml

@@ -1,6 +1,6 @@
 name: compdfkit_flutter
 description: ComPDFKit for Flutter is a comprehensive SDK that allows you to quickly add PDF functionality to Android and iOS Flutter applications.
-version: 2.2.0
+version: 2.2.1
 homepage: https://www.compdf.com
 repository: https://github.com/ComPDFKit/compdfkit-pdf-sdk-flutter
 issue_tracker: https://www.compdf.com/support