1. 首页
  2. Foxit PDF SDK(安卓版)
  3. 开发指南7.4(android)

开发指南7.4(android)

Foxit PDF SDK简介

Foxit PDF SDK

Foxit PDF SDK提供高性能的开发库,帮助软件开发人员使用最流行的开发语言和环境在不同平台 (包括Windows、Mac、Linux、Web、Android、iOS 和 UWP) 的企业版、移动版和云应用程序中添加强大的PDF功能。

使用Foxit PDF SDK的应用开发人员可以利用Foxit强大、标准化的PDF技术安全地显示、创建、编辑、批注、格式化、管理、打印、共享,搜索PDF文档,以及填写PDF文档。此外,Foxit PDF SDK包括一个内置可嵌入的PDF Viewer,使得开发过程更加简单和快速。有关更多详细信息,可以访问网站https://developers.foxitsoftware.cn/pdf-sdk/

在本指南中,我们将重点介绍Foxit PDF SDK for Android 平台。

Foxit PDF SDK for Android

您是否曾经为PDF规范的复杂性而感到不知所措?您是否曾经为被要求在有限的时间内构建一个功能齐全的PDF应用而感到迷茫。如果您的答案是”Yes”, 那么恭喜您!您找到了在业界中快速将PDF功能集成到应用程序中的优选方案。

Foxit PDF SDK for Android 致力于帮助开发人员快速将强大的Foxit PDF技术集成到他们自己的移动端应用程序中。通过Foxit 开发包,即使是对PDF了解有限的开发人员也可以在Android平台上用几行代码快速构建一个专业的PDF 阅读器。

为什么选择Foxit PDF SDK for Android

Foxit 是领先的PDF软件解决方案供应商,专注于PDF显示、编辑、创建、管理以及安全方面。Foxit PDF SDK 开发库已在当今许多知名的应用程序中使用,并且经过长期的测试证明Foxit PDF SDK的质量、性能和功能正是业界大部分应用程序所需要的。

Foxit PDF SDK for Android 提供了快速PDF阅读和Android设备的操作控制。选择Foxit PDF SDK for Android的几大理由:

  • 易于集成

开发人员可以通过几行代码将SDK无缝集成到他们自己的应用程序中。

  • 设计完美

Foxit PDF SDK for Android 拥有简单、干净和友好的风格,并且提供了最好的用户体验。

  • 灵活定制

Foxit PDF SDK for Android 提供了应用层用户界面的源代码,可以帮助开发人员对应用程序的功能和界面外观进行灵活定制。

  • 移动平台鲁棒性

Foxit PDF SDK for Android 提供了OOM (内存溢出) 恢复机制,以确保应用程序在内存有限的移动设备上运行时仍然具备较高的鲁棒性。

  • 基于福昕高保真的PDF渲染引擎

Foxit PDF SDK的核心技术是基于世界众多知名企业所信赖的福昕PDF引擎。福昕强大的PDF引擎可快速解析和渲染文档,不受设备环境的约束。

  • 优秀的技术支持

福昕对自己的开发产品提供了优秀的技术支持,当您在开发关键重要的产品时,可以提供高效的帮助和支持。福昕拥有一支PDF行业优秀的技术支持工程师团队,同时将定期地进行版本更新发布,通过添加新的功能和增强已有的功能来提升用户体验。

Foxit PDF SDK for Android的主要框架

Foxit PDF SDK for Android 由三个元素组成,如下图所示。Foxit PDF SDK的所有移动平台版本共享此结构,这样便于在您的应用程序中集成,以及支持多种手机操作系统和框架。

Foxit PDF SDK for Android, iOS, UWP的三种组成元素

  • PDF Core API

PDF Core API 是SDK的核心部分,建立在福昕强大的底层PDF技术上。它提供了PDF基础功能操作相关的函数,包含了PDF View控件和UI Extensions组件中使用到的PDF核心处理功能,以确保应用程序达到高的性能和效率。该API 可单独用于文档的渲染、分析、文本提取、文本搜索、表单填写、数字签名、压感笔迹 (PSI) 、证书和密码加密、注释的创建和管理等等。

  • PDF View Control

PDF View控件是一个工具类,根据开发人员的需求提供开发人员与渲染的PDF文档进行交互所需要的功能接口。以福昕享有盛誉且使用广泛的PDF渲染技术为核心,View Control支持快速高质量的渲染、缩放、滚动和页面导览功能。该View控件继承于平台相关viewer的类,例如Android.View.ViewGroup, 并且允许进行扩展来满足特定用户的需求。

  • UI Extensions 组件

UI Extensions 组件是一个带内置UI的开源库,支持对内置的文本选择,标记注释、大纲导航、阅读书签、全文检索、填表、文本重排、文档附件、数字/手写签名、文档编辑和密码加密等功能进行自定义。UI Extensions组件中的这些功能是通过使用PDF core API和PDF View Control来实现的。开发人员可以利用这些已有的UI实现快速构建一个PDF阅读器,同时可以根据需要灵活自定义其UI界面。

从4.0版本开始,Foxit PDF SDK for Android对UI Extensions 组件做了一个重大的改变和优化。将基础的UI实现都封装到PDFReader类中,比如面板控件、工具栏设置、以及预警视图对话框等。因此,构建一个功能齐全的PDF阅读器变得越来越简单和容易。此外,用户可以通过一个配置文件灵活自定义他们需要的功能。

从5.0版本开始, 内置UI中的任何元素都可以通过API来进行自定义。该版本为开发人员提供了更高级的APIs和更强大的配置文件来对UI元素进行自定义,比如向工具栏中添加新的功能按钮,或者从工具栏中移除已有的功能按钮,显示/隐藏特定的菜单或者功能面板等。

从6.0版本开始,Foxit PDF SDK for Android移除了PDFReader类,将PDFReader类中封装的APIs移到了UI Extensions组件中。

UI Extensions组件概述

UI Extensions组件采用module机制,将每个功能细化成一个module。当加入UI Extensions时,所有的modules除了LocalModule(用于文件管理)会被默认自动加载。用户可以通过实现Module接口类来自定义module,然后调用UIExtensionsManager#registerModule在当前UIExtensions Manager中进行注册。如果不需要使用时,可以调用UIExtensionsManager#unregisterModule进行反注册。

UIExtensionsManager包含了主框架UI,如top/bottom toolbar, 以及各个模块之间共享的UI组件。同时,各个功能模块也可以通过UIExtensionsManager来进行单独加载。功能模块在加载的时候会对主框架UI进行适配和调整,并且建立起消息事件响应的联系。各个功能模块可能包含了其模块特有的UI组件,同时也会有自己独立的消息事件处理逻辑。UIExtensionsManager也会负责将从View Control组件接收到的消息和事件分发到各个功能模块中去。下面的图片讲述了UIExtensionsManager和modules之间的详细关系。

UIExtensionsManager和 modules之间的关系

Tool handler与annotation handler处理来自PDFViewCtrl的触屏、手势等事件。当触屏和手势事件触发时,PDFViewCtrl会将相应的事件传递给UIExtensionsManager:

  • 如果当前存在tool handler, UIExtensionsManager会将相应的事件传递给当前的tool handle,然后事件处理过程结束。
  • 如果当前有选择annotation,UIExtensionsManager会将相应的事件传递给当前所选择的annotation对应的annotation handler,然后事件处理过程结束。
  • 如果当前不存在tool handler,也没有选中的annotation,那么UIExtensionsManager会将相应的事件传递给selection tool handler。Text Selection tool用于文本选择相关事件的处理,例如选择一段文本添加highlight annotation。Blank Selection tool用于空白处相关事件的处理,例如在空白处添加Note annotation。

备注:Tool Hander和Annotation Handler不会同时响应事件。Tool Handler主要用于annotation的创建(目前不支持Link Annotation的创建)、signature的创建和文本选择。Annotation Handler主要用于annotation的编辑以及表单填写。下图讲述了Tool Handler和Annotation Handler之间的事件响应流程。

Tool Handler和Annotation Handler之间的事件响应流程

Foxit PDF SDK for Android的主要功能

Foxit PDF SDK for Android包括了一些主要的功能,用来帮助应用程序开发人员在快速实现他们所需要的功能的同时减少开发成本。

功能 描述
PDF Document 打开和关闭文件,设置和获取metadata。
PDF Page 解析、渲染、阅读、编辑文档页面。
Render 平台图像设备在bitmap上创建图像渲染引擎。
Reflow 重排页面内容。
Crop 裁剪PDF页面。
Text Select 文本选择。
Text Search 文本搜索,并且支持全文索引搜索。
Outline 定位和链接到文档中的兴趣点。
Reading Bookmark 标记文档中感兴趣的页面和段落位置。
Annotation 创建、编辑和移除annotations。
Layers 添加、编辑和移除PDF层内容。
Attachments 添加、编辑和移除文档级的附件。
Form 支持JavaScript填表,通过XFDF/FDF/XML文件导入和导出表单数据。
支持创建文本域、复选框、单选按钮、组合框、列表框和签名域。
XFA 支持静态和动态XFA。
Signature 签名PDF文档,验证签名,添加或删除签名域。
添加和验证第三方数字签名。
支持签名的长期验证 (LTV)。
Fill 用文本和符号填写扁平化表单(即非交互式表单)
Security 密码和证书加密PDF文档。
Pan and Zoom 调整视图中的放大倍数和位置以匹配Pan&Zoom缩略视图当中的矩形区域。
Print 打印PDF文档。
RMS 支持微软IRMv1和IRMv2标准的RMS解密。
Comparison 对比两个PDF文档,并且标记文档之间的差异。
Scanning 扫描纸质文档,并将其转换为PDF文档。
Speak 支持阅读PDF文档中的文本。
Out of Memory 从内存不足中恢复运行。

备注 OutlinePDF规范中的技术术语,在传统的桌面PDF阅读器中常叫做书签。Reading bookmarks常用于移动端和平板的PDF阅读器中,用来标记阅读进度或者用户感兴趣的段落Reading bookmark在技术上并不是outline,它存储在应用程序中而不是PDF本身。

Foxit PDF SDK for Android支持鲁棒性的PDF应用程序

在有限内存的移动平台上开发鲁棒性的PDF应用程序是具有挑战性的。当内存分配失败,应用程序可能会crash或者意外退出。为了解决这个问题,Foxit PDF SDK for Android提供了一种内存溢出(OOM) 机制。

OOM是Foxit PDF SDK for Android的一个高级功能,因为其本身的复杂性。OOM机制的关键点是Foxit PDF SDK for Android会监视内存的使用情况,并在检测到OOM后自动执行恢复操作。在恢复的过程中,Foxit PDF SDK for Android会自动重新加载文档和页面,将恢复到发生OOM之前的原始状态。这意味着当前阅读的页面和位置,以及页面阅读模式(单页或者连续页面)都能够恢复,但是编辑相关的内容将会丢失。

Foxit PDF SDK for Android在PDFViewCtrl类中提供一个属性”shouldRecover“。默认情况下shouldRecover“为 “true“。如果在检测到OOM时您不想开启自动恢复机制,您可以将”shouldRecover“设置为 “false“,如下所示:

PDFViewCtrl pdfViewerCtrl = new PDFViewCtrl(getActivity().getApplicationContext());
pdfViewerCtrl.shouldRecover = false;

此时,应用程序将会抛出异常,可能会crash或者意外退出。

评估

用户可申请下载Foxit PDF SDK的试用版本进行试用评估。试用版除了有试用期10天时间的限制以及生成的PDF页面上会有试用水印以外,其他都和标准认证版一样。当试用期到期后,用户需联系福昕销售团队并购买licenses以便继续使用Foxit PDF SDK.

授权

程序开发人员需购买licenses授权才能在其解决方案中使用Foxit PDF SDK。 Licenses授予用户发布基于Foxit PDF SDK开发的应用程序的权限。然而,在未经福昕软件公司授权下,用户不能将Foxit PDF SDK包中的任何文档、示例代码以及源代码分发给任何第三方机构。

关于此文档

此文档适用于需要将Foxit PDF SDK for Android集成到自己的应用程序中的开发人员。它旨在介绍以下章节:

  • Section 1: 介绍Foxit PDF SDK,特别是Android平台的SDK。
  • Section 2: 说明包的结构,以及运行demos。
  • Section 3: 介绍如何快速创建功能齐全的PDF阅读器。
  • Section 4: 介绍如何自定义用户界面。
  • Section 5: 介绍如何使用Foxit PDF SDK core API。
  • Section 6: 介绍如何创建自定义工具。
  • Section 7: 展示如何使用Cordova实现Foxit PDF SDK
  • Section 8: 展示如何使用React Native实现Foxit PDF SDK
  • Section 9: 展示如何使用Xamarin实现Foxit PDF SDK
  • Section 10: 列出常见问题
  • Section 11: 提供技术支持信息

 

入门指南

安装并集成Foxit PDF SDK for Android非常简单。您只需要几分钟就能见证其强大的功能。本指南主要介绍如何在Android平台使用Foxit PDF SDK。本章的主要内容是包结构的介绍以及如何运行demo。

系统要求

Android 设备要求

  • Android 4.4 (API 19) or higher
  • 32/64-bit ARM (armeabi-v7a/arm64-v8a) or 32/64-bit Intel x86 CPU

Android Studio 3.2 or newer (支持AndroidX)

包中Demos的运行环境:

  • Android Studio 3.2
  • JDK 1.8
  • Gradle Version 4.6
  • Gradle Build Tool 3.2

备注 7.2版本开始,Foxit PDF SDK for Android将只支持AndroidX,而不再支持Android support library

包结构说明

下载 “foxitpdfsdk_7_4_android.zip”包,解压到一个新的目录如 “foxitpdfsdk_7_4_android”,如Figure 2-1所示。其中解压包中包括如下的内容:

docs: API手册,开发文档和升级说明文档
icc_profile 输出预览 (output preview) 功能所使用的默认icc profile文件
libs: License文件,AAR,UI Extensions组件源代码
samples: Android 示例工程
getting_started_android.pdf: Foxit PDF SDK for Android 快速入门
legal.txt: 法律和版权信息
release_notes.txt: 发布信息

Figure 2-1

如Figure 2-2所示的”libs”文件夹下是Foxit PDF SDK for Android的核心组件,以及用于支持微软RMS的第三方库。

Figure 2-2

  • uiextensions_src 工程 – 在”libs”文件夹下。它是一个开源库,包含了一些即用型的UI模块实现,可以帮助开发人员快速将功能齐全的PDF阅读器嵌入到他们的Android应用中。当然,开发人员也不是必须要使用默认的UI,可以通过”uiextensions_src”工程为特定的应用灵活自定义和设计UI。
  • FoxitRDK.aar – 包含JAR包,其中包括Foxit PDF SDK for Android的所有Java APIs,以及”.so”库。”.so”库是SDK的核心包含了Foxit PDF SDK for Android的核心函数。它针对每种架构单独编译,当期支持armeabi-v7a, arm64-v8a, x86, 和 x86_64架构。
  • FoxitRDKUIExtensions.aar – 由”libs”目录下的 “uiextensions_src“工程 编译生成。包括JAR包,内置UI实现,以及UI所需要的资源文件,如图片,字符串、颜色值、布局文件以及其他Android UI资源。
  • pdfscan工程 – 是一个开源库,包含了扫描功能相关的UI实现,可以帮助开发人员快速将扫描功能集成到他们的Android应用中,或者根据需要自定义扫描功能的UI。
  • FoxitMobileScanningRDK.aar – 提供扫描功能所需要的库。
  • FoxitPDFScan-UI.aar – 提供实现扫描功能所需UI的 Android Activities。
  • RMSSDK-4.2-release.aar – 微软权限管理系统的软件开发包。更多详细信息,请参考https://www.microsoft.com/en-ie/download/details.aspx?id=43673.
  • rms-sdk-ui.aar – 提供实现RMS SDK功能所需UI的Android Activities。更多详细信息,请参考https://github.com/AzureAD/rms-sdk-ui-for-android.

备注为了减小FoxitRDKUIExtensions.aar的文件大小,Foxit PDF SDK for Android uiextensions_src工程中使用shrink-code技术。如果您在编译Uiextensions_src工程时,不需使用shrink-code,您可以在App下的build.gradle中通过设置minifyEnabled false来进行禁用。关于shrink-code参考https://developer.android.com/studio/build/shrink-code.html.

到此,您应该初步了解Foxit PDF SDK for Android结构和内容,接下来我们将进行详细介绍。

运行demo

下载和安装Android Studio IDE (https://developer.android.com/studio/index.html).

备注:在本指南中,不具体介绍Android StudioAndroid SDK JDK的安装步骤。如果您还没有安装,参考Android Studio的开发官网。

Foxit PDF SDK for Android 为开发人员提供了三种不同类型的demos,用来展示如何调用SDK,如Figure 2-3所示。

Figure 2-3

Function demo

Function demo用来展示如何使用Foxit PDF SDK for Android的core API来实现一些PDF特定的功能。该demo包括如下的功能:

  • pdf2txt: 从PDF文档中提取文本,并保存到一个TXT文件中。
  • outline: 编辑大纲(也称为书签)的外观和主题。
  • annotation: 向PDF页面添加annotations。
  • docinfo: 导出PDF文档信息到TXT文件中。
  • render: 将指定的PDF页面渲染包结构说明为位图。
  • signature: 向PDF中添加签名,对PDF签名和验证签名。

在Android Studio中运行该demo,请按如下的步骤:

a)在Android Studio中打开demo,通过 “File -> New -> Import Project…” 或者 “File -> Open…”, 然后找到function_demo所在的位置,选择function_demo。点击”OK”。

b)开启一个Android设备或者模拟器(AVD)。在本章中,将使用模拟器AVD 9.0来运行demo。Demo所需要的测试文件在demo运行时会被自动拷贝到模拟器的存储卡中,测试文件 在 “samples/test_files”文件夹下。

c)点击”Run -> Run ‘app'” 来运行demo。当在模拟器上安装完APK后,在弹出的窗口点击”Allow” 允许demo访问设备上的文件。然后您就能看到如Figure 2-4所示的功能选项。

Figure 2-4

d)点击上图中的功能按钮去执行相应的操作。例如,点击 “pdf2txt”, 则会弹出如Figure 2-5所示的消息框,提示转换后的文本文件保存的位置。请运行demo并自由体验其功能。

Figure 2-5

Viewer control demo

Viewer control demo阐述如何使用Foxit PDF SDK for Android实现与View Control功能层相关的功能,比如操作annotations (注释、高亮、下划线、删除线、波浪线等) ,修改布局,文本搜索,大纲和页面缩略图。该demo的代码逻辑结构非常清晰和简单,开发人员可以快速定位PDF应用,比如PDF阅读器中某个功能的具体实现。并且通过这个demo,开发人员可以更进一步的接触和了解Foxit PDF SDK for Android所提供的APIs。

在Android Studio中运行该demo,请参考Function demo 中的步骤。

Viewer control demo不会自动拷贝测试文件到Android 设备或者模拟器中。该demo使用”sample.pdf” (在”samples/test_files”文件夹下)作为测试文件,请确保在运行demo之前,您已经将该文件添加到Android设备或者模拟器中创建的”input_files” (或者”FoxitSDK”,取决于您在demo中的设置)。

Demo成功运行后会如Figure 2-6所示。这里,使用AVD 9.0来运行demo。

Figure 2-6

点击页面上的任意位置,会出现上下文操作栏,然后点击 (更多按钮) 去查看更多的功能操作选项,如Figure 2-7所示。

Figure 2-7

现在,可以选择其中一个选项执行,并且查看其结果。比如,点击”Outline”,您会看到该测试文档的大纲目录(outline在PDF规范中指的是书签),如Figure 2-8所示。您可以自行尝试其他的功能选项。

Figure 2-8

Complete PDF viewer demo

Complete PDF Viewer demo阐述了如何通过使用Foxit PDF SDK for Android实现一个功能齐全的PDF阅读器,该阅读器几乎可以作为实际移动端的PDF阅读器使用。而且从6.0版本开始,支持多文件阅读模式。该demo使用了Foxit PDF SDK for Android所提供的所有功能和内置UI实现。

在Android Studio中运行该demo,请参考Function demo 中的步骤。

在运行该demo时,”samples\complete_pdf_viewer\app\src\main\assets”目录下的”complete_pdf_viewer_guide_android.pdf” 和 “Sample.pdf” 文件将会被自动拷贝到模拟器的”FoxitSDK”文件夹下。

这里,将使用AVD 9.0 来运行demo。当成功运行后,屏幕会列出”complete_pdf_viewer_guide_android.pdf” 和 “Sample.pdf” 文档。如果您想要阅读多个文档,点击 切换到多文档阅读模式 (如Figure 2-9所示)。

备注“complete_pdf_viewer_guide_android.pdf” 和 “Sample.pdf”会自动部署到您运行的设备中,因此您不需要手动将其添加到设备中。如果您想使用其他的PDF文档来测试该demo,您需要手动将其添加到设备的SD卡中。

Figure 2-9

点击YES切换到多文档阅读模式。选择”complete_pdf_viewer_guide_android.pdf” 文档,点击Back按钮,再选择”Sample.pdf”,如Figure 2-10所示。现在,您可以通过切换选项卡浏览这两个文档。

Figure 2-10

该demo实现了一个功能齐全的PDF阅读器,请随意体验。

例如,它提供了页面缩略图功能。您可以点击View菜单,选择Thumbnail 如Figure 2-11所示,然后您可以看到如Figure 2-12所示的文档的缩略图。

Figure 2-11

Figure 2-12

 

快速构建一个功能齐全的PDF阅读器

Foxit PDF SDK for Android 将所有的UI实现(包括应用程序的基本UI和即用型UI功能模块)封装在UI Extensions组件中,因此开发人员可以轻松快速通过几行代码构建一个功能齐全的PDF阅读器。本章将提供详细的教程来帮助您快速开始使用Foxit PDF SDK for Android在Android平台创建一个功能齐全的PDF阅读器。其主要包括以下的步骤:

创建一个新的Android工程

在本指南中,使用Android Studio 3.5.3,以及Android API 28.

打开Android Studio,选择File -> New -> New Project…,在Choose your project向导中,选择 “Empty Activity”Figure 3-1所示。然后点击Next

Figure 3-1

然后填写Configure your project对话框,如Figure 32所示。填写完后,点击Finish

Figure 3-2

集成Foxit PDF SDK for Android到您的应用程序

备注:在本章中,我们将使用默认的内置UI实现来开发该应用程序,为了简单和方便 (直接使用UI Extensions组件,不需要源代码工程),我们只需要添加以下的文件到PDFReader工程中。

  • FoxitRDK.aar – 包含JAR包,其中包括Foxit PDF SDK for Android的所有Java APIs,以及”.so”库。”.so”库是SDK的核心包含了Foxit PDF SDK for Android的核心函数。它针对每种架构单独编译,当期支持armeabi-v7a, arm64-v8a, x86, 和 x86_64架构。
  • FoxitRDKUIExtensions.aar – 由”libs”目录下的 “uiextensions_src“工程 编译生成。包括JAR包,内置UI实现,以及UI所需要的资源文件,如图片,字符串、颜色值、布局文件以及其他Android UI资源。

技巧:在接下来的 初始化Foxit PDF SDK for Android使用PDFViewCtrl显示PDF文档 以及 打开一个RMS加密的文档三小节中,不需要使用UI Extensions组件 (FoxitRDKUIExtensions.aar), 因此您可以先FoxitRDK.aar添加到工程中。然后在您需要使用UI Extensions组件时再添加FoxitRDKUIExtensions.aar,比如在 使用UI Extensions组件构建一个功能齐全的PDF阅读器章节中。

添加上述的两个AAR文件到PDFReader工程,请切换到”Project”面板,然后按照如下的步骤:

a)从下载包的”libs”文件夹下拷贝”FoxitRDK.aar” 和 “FoxitRDKUIExtensions.aar“文件到”PDFReader\app\libs”文件夹下。

备注

  • 如果需要支持微软RMS,您需要同时拷贝 RMSSDK-4.2-release.aar rms-sdk-ui.aar 文件。
  • 如果需要支持scanning功能,需要同时拷贝 FoxitMobileScanningRDK.aarFoxitPDFScan-UI.aar文件

同步PDFReader工程,然后该工程将如Figure 3-3所示。

Figure 3-3

b)将”libs”目录定义为repository。在app下面的build.gradle文件中,添加如下的配置:

build.gradle:
repositories {
    flatDir {
        dirs 'libs'
    }
}

c)启用Multi-Dex。在app下面的build.gradle文件中,添加如下的代码:

...
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.foxit.pdfreader"
        minSdkVersion 16
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    }
    ...
}
...

dependencies {
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
}
...

d)将Foxit PDF SDK for Android作为工程的依赖项。在app下面的 “build.gradle” 文件中,添加”FoxitRDK.aar“, “FoxitRDKUIExtensions.aar” 以及相关支持的库到dependencies。为简单起见,如下所示更新dependencies:

dependencies {
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation 'com.google.android.material:material:1.1.0'
    implementation(name: 'FoxitRDK', ext: 'aar')
    implementation(name: 'FoxitRDKUIExtensions', ext: 'aar')
    implementation(name: 'FoxitMobileScanningRDK', ext: 'aar')
    implementation(name: 'FoxitPDFScan-UI', ext: 'aar')
    implementation 'com.edmodo:cropper:1.0.1'
    implementation('com.microsoft.aad:adal:1.16.3') {}
    implementation(name: 'RMSSDK-4.2-release', ext: 'aar')
    implementation(name: 'rms-sdk-ui', ext: 'aar')
    implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
    // RxJava
    implementation "io.reactivex.rxjava2:rxjava:2.2.16"
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
    implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
    
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
}

备注:

  • (必选) Foxit PDF SDK for Android依赖于google.android.material,因此您需要添加如下的条目dependencies中:

implementation ‘com.google.android.material:material:1.1.0’

  • (可选) 如果您需要使用截图 (Snapshot)功能 (如在Complete PDF viewer demo,在右上方点击 可以看到截图功能选项)您需要添加如下的条目dependencies

 implementation  ‘com.edmodo:cropper:1.0.1’

  • (可选) 如果您需要打开RMS加密PDF文档,您需要添加如下的条目到dependencies

implementation(‘com.microsoft.aad:adal:1.16.3’) {}

implementation(name: ‘RMSSDK-4.2-release’, ext: ‘aar’)

implementation(name: ‘rms-sdk-ui’, ext: ‘aar’)

备注:您最好使用RMS SDK 4.2 ADAL 1.16.3版本,否则可能会导致兼容性问题。

  • (可选) 如果您需要使用扫描 (scanning) 功能 (比如,在Complete PDF viewer demo的启动页面,可以看到扫描功能按钮选项 ),则需要添加如下的条目到dependencies

implementation(name: ‘FoxitMobileScanningRDK’, ext: ‘aar’)

implementation(name: ‘FoxitPDFScan-UI, ext: ‘aar’)

implementation ‘com.nostra13.universalimageloader:universal-image-loader:1.9.5’

  • (可选) 如果您需要使用对比功能 (比如,在Complete PDF viewer demo的启动页面,点击右上方的,可以看到对比功能选项) 需要添加如下的条目到dependencies

implementation “io.reactivex.rxjava2:rxjava:2.2.16”

implementation ‘io.reactivex.rxjava2:rxandroid:2.1.1’

  • (可选) 如果您需要使用签名功能,则需要添加如下的条目到dependencies

implementation ‘org.bouncycastle:bcpkix-jdk15on:1.64’

implementation ‘org.bouncycastle:bcprov-jdk15on:1.64’

此处,我们将如上所有的支持库都添加到dependencies中,因为稍后我们将构建一个功能齐全的PDF阅读器,该阅读器包含Foxit PDF SDK for Android所提供的所有的功能。在APP下的”build.gradle”中设置完成后,同步工程,然后您可以在External Libraries下看到

FoxitRDK.aar“, “FoxitRDKUIExtensions.aar“, “FoxitMobileScanningRDK.aar“, “FoxitPDFScan-UI.aar“, “universal-image-loader“, “material“, “cropper“, “rxjava“, rxandroid , bcpkix-jdk15on, bcprov-jdk15onRMS相关的包。

“build.gradle”的完整代码如下。

build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.foxit.pdfreader"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        multiDexEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

repositories {
    flatDir {
        dirs 'libs'
    }
}

dependencies {
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation 'com.google.android.material:material:1.1.0'
    implementation(name: 'FoxitRDK', ext: 'aar')
    implementation(name: 'FoxitRDKUIExtensions', ext: 'aar')
    implementation(name: 'FoxitMobileScanningRDK', ext: 'aar')
    implementation(name: 'FoxitPDFScan-UI', ext: 'aar')
    implementation 'com.edmodo:cropper:1.0.1'
    implementation('com.microsoft.aad:adal:1.16.3') {}
    implementation(name: 'RMSSDK-4.2-release', ext: 'aar')
    implementation(name: 'rms-sdk-ui', ext: 'aar')
    implementation 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
    // RxJava
    implementation "io.reactivex.rxjava2:rxjava:2.2.16"
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
    implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
}

备注使用AndroidXcompileSdkVersion最低需要设置成28在本工程中,我们将compileSdkVersion targetSdkVersion设置API 28如果您也使用API 28,请确保您已经安装了Android 9.0API 28 SDK平台。如果没有,请首先打开Android SDK Manager进行下载和安装。

初始化Foxit PDF SDK for Android

在调用任何API之前,应用程序必须使用license初始化Foxit PDF SDK for Android。Library.initialize(sn, key)函数用于SDK库的初始化。试用license文件在下载包的”libs”文件夹下。当试用期结束后,您需要购买正式license以继续使用该SDK。下面是SDK库初始化的示例代码。在下一节中将介绍该代码在PDFReader工程中的位置。

import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Library;


...


int errorCode = Library.initialize("sn", "key");
if (errorCode != Constants.e_ErrSuccess)
    return;

备注:参数 sn的值在rdk_sn.txt (SN=后面的字符串)key的值在 rdk_key.txt (Sign=后面的字符串)

使用PDFViewCtrl显示PDF文档

到目前为止,我们已经在PDFReader工程中添加了Foxit PDF SDK for Android库,并且完成了SDK库的初始化。现在,我们将使用PDFViewCtrl通过几行代码来显示一个PDF文档。

备注:如果需要显示一个PDF文档,则不需要UI Extensions组件

显示一个PDF文档,请按照如下的步骤:

a)实例化一个PDFViewCtrl对象来显示一个PDF文档。

在MainActivity.java中,实例化一个PDFViewCtrl对象,调用PDFViewCtrl.openDoc函数打开和渲染PDF文档。

import com.foxit.sdk.PDFViewCtrl;
...
private PDFViewCtrl pdfViewCtrl = null;
...
pdfViewCtrl = new PDFViewCtrl(this);

String path = "/mnt/sdcard/input_files/Sample.pdf";
pdfViewCtrl.openDoc(path, null);
SetContentView(pdfViewCtrl);

备注:请确保您已经将 Sample.pdf文档添加到用于运行该工程的Android设备或者模拟器中已创建的 input_files文件夹。

更新MainActivity.java

package com.foxit.pdfreader;


import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;


import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Library;

public class MainActivity extends AppCompatActivity {

    private PDFViewCtrl pdfViewCtrl = null;

    // The value of "sn" can be found in the "rdk_sn.txt".
    // The value of "key" can be found in the "rdk_key.txt".
    private static String sn = " ";
    private static String key = " ";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // initialize the library.
        int errorCode = Library.initialize(sn, key);
        if (errorCode != Constants.e_ErrSuccess)
            return;
        pdfViewCtrl = new PDFViewCtrl(this);
        String path = "/mnt/sdcard/input_files/Sample.pdf";
        pdfViewCtrl.openDoc(path, null);

        setContentView(pdfViewCtrl);
    }
}

b)设置Android设备或者模拟器的SD卡的读写权限您需要添加额外代码申请授权运行时权限

MainActivity.java中,添加如下的代码:

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;

import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;


...


private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSIONS_STORAGE = {
        Manifest.permission.READ_EXTERNAL_STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
};


...


// Require the authorization of runtime permissions.


 if (Build.VERSION.SDK_INT >= 23) {
    int permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
    if (permission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
        return;
    }
}


...


@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        // Open and Reader a PDF document.
        String path = "/mnt/sdcard/input_files/Sample.pdf";
        pdfViewCtrl.openDoc(path, null);
        setContentView(pdfViewCtrl);
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
}

然后,MainActivity.java的全部代码如下所示:

package com.foxit.pdfreader;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;


import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Library;

public class MainActivity extends AppCompatActivity {

    private PDFViewCtrl pdfViewCtrl = null;

    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    // The value of "sn" can be found in the "rdk_sn.txt".
    // The value of "key" can be found in the "rdk_key.txt".
    private static String sn = " ";
    private static String key = " ";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Initialize the library.
        int errorCode = Library.initialize(sn, key);
        if (errorCode != Constants.e_ErrSuccess)
            return;
        // Instantiate a PDFViewCtrl object.
        pdfViewCtrl = new PDFViewCtrl(this);

        // Require the authorization of runtime permissions.
        if (Build.VERSION.SDK_INT >= 23) {
            int permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (permission != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                return;
            }
        }

        // Open and Render a PDF document.
        String path = "/mnt/sdcard/input_files/Sample.pdf";
        pdfViewCtrl.openDoc(path, null);
        setContentView(pdfViewCtrl);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Open and Render a PDF document.
            String path = "/mnt/sdcard/input_files/Sample.pdf";
            pdfViewCtrl.openDoc(path, null);
            setContentView(pdfViewCtrl);
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

在本节中,使用AVD 9.0 (API 28) 来编译和运行该工程。

现在,我们已经使用Foxit PDF SDK for Android通过几行代码完成了一个显示PDF文档的简单的Android应用程序。下一步是运行该工程。

当编译完项目并在模拟器上安装APK后,在弹出的窗口点击”Allow” 允许工程访问设备上的文件。然后您将看到”Sample.pdf”文档显示如Figure 3-4所示。该示例应用程序具有一些基本的PDF功能,比如放大/缩小和翻页。您可以进行体验。

Figure 3-4

打开一个RMS加密的文档

从6.2.1版本开始,Foxit PDF SDK for Android支持打开微软RMS加密的文档。

打开RMS加密的文档,您需要注意以下几点:

1)在项目中添加RMS相关库依赖。请参考”集成Foxit PDF SDK for Android到您的应用程序”。

2)在打开RMS加密的文档时需要有UI相关的操作,因此在打开文档之前需要设置关联的activity。

pdfViewCtrl.setAttachedActivity(activity);

3)处理来自UI操作的activity结果。在onActivityResult函数中调用API “pdfViewCtrl.handleActivityResult()

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    pdfViewCtrl.handleActivityResult(requestCode, resultCode, data);
}

基于上一节”使用PDFViewCtrl显示PDF文档“,更新整个MainActivity.java,如下所示:

package com.foxit.pdfreader;


import android.Manifest;


import android.content.Intent;


import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;


import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Library;

public class MainActivity extends AppCompatActivity {

    private PDFViewCtrl pdfViewCtrl = null;

    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    // The value of "sn" can be found in the "rdk_sn.txt".
    // The value of "key" can be found in the "rdk_key.txt".
    private static String sn = " ";
    private static String key = " ";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // initialize the library.
        int errorCode = Library.initialize(sn, key);
        if (errorCode != Constants.e_ErrSuccess)
            return;

        // Instantiate a PDFViewCtrl object.
        pdfViewCtrl = new PDFViewCtrl(this);


        // Set the associated activity for RMS UI operations.
        pdfViewCtrl.setAttachedActivity(this);


        // Require the authorization of runtime permissions.
        if (Build.VERSION.SDK_INT >= 23) {
            int permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (permission != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                return;
            }
        }

        // Open and Render a PDF document.
        String path = "/mnt/sdcard/input_files/Sample_RMS.pdf";
        pdfViewCtrl.openDoc(path, null);
        setContentView(pdfViewCtrl);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Open and Render a PDF document.
            String path = "/mnt/sdcard/input_files/Sample_RMS.pdf";
            pdfViewCtrl.openDoc(path, null);
            setContentView(pdfViewCtrl);
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }


 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);


        pdfViewCtrl.handleActivityResult(requestCode, resultCode, data);
    }
}

备注:请确保您已经将一个RMS加密的文档比如 Sample_RMS.pdf添加到用于运行该工程的Android设备或者模拟器中已创建的 input_files文件夹。

当编译完项目并在模拟器上安装APK后,在弹出的窗口点击”Allow” 允许工程访问设备上的文件。然后您将看到如Figure 3-5所示的界面,提示您输入组织电子邮件,密码,然后您就可以打开该RMS加密的文档。

Figure 3-5

使用UI Extensions组件构建一个功能齐全的PDF阅读器

Foxit PDF SDK for Android 带有内置的UI设计,包括应用程序的基础UI和功能模块UI。内置UI通过Foxit PDF SDK for Android实现,并且封装在UI Extensions组件中。因此,构建一个功能齐全的PDF阅读器变得越来越简单。您只需要实例化一个UIExtensionsManager对象,然后将其设置给PDFViewCtrl。

实例化一个UIExtensionsManager对象,并且设置给PDFViewCtrl

在”MainActivity.java”文件中,我们将添加包含UIExtensionsManager所需的代码。代码片段如下所示,后面您将看到”MainActivity.java”的完整代码。

a)将系统主题设置为 No Title模式并且将窗口设置为全屏

备注UI Extensions组件自定义用户界面,因此您需要将系统主题设置为No Title模式,以及将窗口设置为全屏。否则,内置功能的布局可能会被影响。

import android.view.Window;
import android.view.WindowManager;


...
// Turn off the title at the top of the screen.
this.requestWindowFeature(Window.FEATURE_NO_TITLE);
// Set the window to Fullscreen.
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

确保将 PDFReader\app\src\main\res\values\styles.xml文件中的theme style设置Theme.AppCompat.Light.NoActionBar如下所示:

b)添加代码实例化一个UIExtensionsManager对象,并且将其设置给PDFViewCtrl

import com.foxit.uiextensions.UIExtensionsManager;


...
private UIExtensionsManager uiExtensionsManager = null;


...
uiExtensionsManager = new UIExtensionsManager(this.getApplicationContext(), pdfViewCtrl);
uiExtensionsManager.setAttachedActivity(this);
uiExtensionsManager.onCreate(this, pdfViewCtrl, savedInstanceState);
pdfViewCtrl.setUIExtensionsManager(uiExtensionsManager);

c)打开和渲染一个PDF文档,设置内容视图

调用UIExtensionsManager.openDocument()函数打开和渲染PDF文档,而不是调用PDFViewCtrl.openDoc()函数。

import com.foxit.uiextensions.UIExtensionsManager;


...

String path = “/mnt/sdcard/input_files/Sample.pdf”;
uiExtensionsManager.openDocument(path, null);
setContentView(
uiExtensionsManager.getContentView());

更新MainActivity.java

备注:添加Activity生命周期事件,否则某些功能可能无法正常使用

package com.foxit.pdfreader;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Window;
import android.view.WindowManager;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.annotation.NonNull;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Library;
import com.foxit.uiextensions.UIExtensionsManager;



public class MainActivity extends AppCompatActivity {

    private PDFViewCtrl pdfViewCtrl = null;
    private UIExtensionsManager uiExtensionsManager = null;

    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    // The value of "sn" can be found in the "rdk_sn.txt".
    // The value of "key" can be found in the "rdk_key.txt".
    private static String sn = " ";
    private static String key = " ";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // initialize the library.
        int errorCode = Library.initialize(sn, key);
        if (errorCode != Constants.e_ErrSuccess)
            return;
        // Turn off the title at the top of the screen.
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        // Set the window to Fullscreen.
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // Instantiate a PDFViewCtrl object.
        pdfViewCtrl = new PDFViewCtrl(this);

        // Set the associated activity for RMS UI operations.
        pdfViewCtrl.setAttachedActivity(this);
        // Initialize a UIExtensionManager object and set it to PDFViewCtrl.
        uiExtensionsManager = new UIExtensionsManager(this.getApplicationContext(), pdfViewCtrl);
        uiExtensionsManager.setAttachedActivity(this);
        uiExtensionsManager.onCreate(this, pdfViewCtrl, savedInstanceState);
        pdfViewCtrl.setUIExtensionsManager(uiExtensionsManager);

        // Require the authorization of runtime permissions.
        if (Build.VERSION.SDK_INT >= 23) {
            int permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (permission != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                return;
            }
        }

        // Open and Render a PDF document.
        String path = "/mnt/sdcard/input_files/Sample.pdf";
        uiExtensionsManager.openDocument(path, null);
        setContentView(uiExtensionsManager.getContentView());
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Open and Render a PDF document.
            String path = "/mnt/sdcard/input_files/Sample.pdf";
            uiExtensionsManager.openDocument(path, null);
            setContentView(uiExtensionsManager.getContentView());
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   super.onActivityResult(requestCode, resultCode, data);
        pdfViewCtrl.handleActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onStart() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onStart(this);
        }
        super.onStart();
    }

    @Override
    public void onStop() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onStop(this);
        }
        super.onStop();
    }

    @Override
    public void onPause() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onPause(this);
        }
        super.onPause();
    }

    @Override
    public void onResume() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onResume(this);
        }
        super.onResume();
    }

    @Override
    protected void onDestroy() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onDestroy(this);
        }
        super.onDestroy();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onConfigurationChanged(this, newConfig);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (uiExtensionsManager != null && uiExtensionsManager.onKeyDown(this, keyCode, event))
            return true;
        return super.onKeyDown(keyCode, event);
    }
}

更新AndroidManifest.xml

添加“<uses-permission android:name=“android.permission.CAMERA”/>”授权工程访问摄像机的权限。

添加“<uses-permission android:name=“android.permission.RECORD_AUDIO”/>”授权工程录制音频和视频的权限。如果不添加,音频和视频功能将无法正常使用。

 

添加“<uses-permission android:name=“android.permission.SYSTEM_ALERT_WINDOW”/>”授权手机设备浮动窗口的权限。如果不添加,Pan and Zoom 功能可能无法正常使用。

添加android:configChanges=“keyboardHidden|orientation|locale|layoutDirection|screenSize”>”属性,以确保在旋转屏幕时只执行onConfigurationChanged()函数,而不会重新调用activity生命周期。如果不添加,签名功能可能无法正常使用。

更新AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.foxit.pdfreader">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />


    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
         android:configChanges="keyboardHidden|orientation|locale|layoutDirection|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

运行工程

在本节中,使用AVD 9.0 (API 28) 来编译和运行该工程。当编译完项目并在模拟器上安装APK后,在弹出的窗口点击”Allow” 允许工程访问设备上的文件。然后您将看到”Sample.pdf”文档显示如Figure 3-6所示。到目前为止,该工程是一个功能齐全的PDF阅读器,包含Complete PDF viewer demo中的所有功能,并且支持打开RMS加密的文档。请您随意体验。

Figure 3-6

基于功能齐全的PDF阅读器添加扫描功能

扫描功能是一个单独的模块,没有封装在UI Extensions组件中。因此,如果您需要在工程中使用该功能,那么请添加如下的核心代码来调用scan模块:

import com.foxit.pdfscan.PDFScanManager;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
...
// Initialize the scan module.
long framework1 = 0;
long framework2 = 0;
PDFScanManager.initializeScanner(this.getApplication(), framework1, framework2);

long compression1 = 0;
long compression2 = 0;
PDFScanManager.initializeCompression(this.getApplication(), compression1, compression2);
// Start scanning.
FragmentManager manager = this.getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
Fragment targetFragment = manager.findFragmentByTag("ScannerList");
if (targetFragment != null) {
    transaction.remove(targetFragment);
}

DialogFragment scannerDialog = PDFScanManager.createScannerFragment(null);
transaction.add(scannerDialog, "ScannerList");
transaction.commitAllowingStateLoss();

对于PDFScanManager.initializeScannerPDFScanManager.initializeCompression接口,如果您将第二和第三个参数设置为0,则扫描后的图片会带有水印。如果您需要去掉水印,请联系Foxit销售或者技术支持团队来获取授权的key。
基于上一节,添加一个新的button来调用scan模块。
更新MainActivity.java所示:
(假设您已经将 “samples\complete_pdf_viewer\app\src\main\res\drawable-hdpi” 文件夹下的”fx_floatbutton_scan.png” 拷贝到 “PDFReader\app\src\main\res\drawable”)

package com.foxit.pdfreader;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Library;
import com.foxit.uiextensions.UIExtensionsManager;
import com.foxit.pdfscan.PDFScanManager;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;


public class MainActivity extends AppCompatActivity {

    private PDFViewCtrl pdfViewCtrl = null;
    private UIExtensionsManager uiExtensionsManager = null;

    private static final int REQUEST_EXTERNAL_STORAGE = 1;
    private static final String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    // The value of "sn" can be found in the "rdk_sn.txt".
    // The value of "key" can be found in the "rdk_key.txt".
    private static String sn = " ";
    private static String key = " ";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Initialize the library.
        int errorCode = Library.initialize(sn, key);
        if (errorCode != Constants.e_ErrSuccess)
            return;

        // Turn off the title at the top of the screen.
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);

        // Set the window to Fullscreen.
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);

        // Instantiate a PDFViewCtrl object.
        pdfViewCtrl = new PDFViewCtrl(this);

        // Set the associated activity for RMS UI operations.
        pdfViewCtrl.setAttachedActivity(this);

        uiExtensionsManager = new UIExtensionsManager(this.getApplicationContext(), pdfViewCtrl);
        uiExtensionsManager.setAttachedActivity(this);
        uiExtensionsManager.onCreate(this, pdfViewCtrl, savedInstanceState);
        pdfViewCtrl.setUIExtensionsManager(uiExtensionsManager);

        // Require the authorization of runtime permissions.
        if (Build.VERSION.SDK_INT >= 23) {
            int permission = ContextCompat.checkSelfPermission(this.getApplicationContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
            if (permission != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                return;
            }
        }

        // Open and Render a PDF document.
        String path = "/mnt/sdcard/input_files/Sample.pdf";
        uiExtensionsManager.openDocument(path, null);

        RelativeLayout rootView = new RelativeLayout(getApplicationContext());
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup
                .LayoutParams.MATCH_PARENT);
        rootView.addView(uiExtensionsManager.getContentView(), layoutParams);
        rootView.addView(getScanButton());
        setContentView(rootView);
    }

    // Get a scan button.
    private View getScanButton() {
        ImageView ivScan = new ImageView(this);
        ivScan.setImageResource(R.drawable.fx_floatbutton_scan);
        ivScan.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                if (!PDFScanManager.isInitializeScanner()) {
                    long framework1 = 0;
                    long framework2 = 0;
                    PDFScanManager.initializeScanner(MainActivity.this.getApplication(), framework1, framework2);
                }

                if (!PDFScanManager.isInitializeCompression()) {
                    long compression1 = 0;
                    long compression2 = 0;
                    PDFScanManager.initializeCompression(MainActivity.this.getApplication(), compression1, compression2);
                }

                if (PDFScanManager.isInitializeScanner() && PDFScanManager.isInitializeCompression()) {
                    FragmentManager manager = MainActivity.this.getSupportFragmentManager();
                    FragmentTransaction transaction = manager.beginTransaction();
                    Fragment targetFragment = manager.findFragmentByTag("ScannerList");
                    if (targetFragment != null) {
                        transaction.remove(targetFragment);
                    }

                    DialogFragment scannerDialog = PDFScanManager.createScannerFragment(null);
                    transaction.add(scannerDialog, "ScannerList");
                    transaction.commitAllowingStateLoss();
                } else {
                    Toast.makeText(getApplicationContext(), "You are not authorized to use this add-on module, please contact us for upgrading your license.", Toast.LENGTH_SHORT).show();
                }
            }
        });

        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);

        layoutParams.bottomMargin = 120;
        layoutParams.rightMargin = 50;
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
        layoutParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
        ivScan.setLayoutParams(layoutParams);
        return ivScan;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == REQUEST_EXTERNAL_STORAGE && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Open and Render a PDF document.
            String path = "/mnt/sdcard/input_files/Sample.pdf";
            uiExtensionsManager.openDocument(path, null);
            setContentView(uiExtensionsManager.getContentView());
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        pdfViewCtrl.handleActivityResult(requestCode, resultCode, data);
    }

    @Override
    public void onStart() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onStart(this);
        }
        super.onStart();
    }

    @Override
    public void onStop() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onStop(this);
        }
        super.onStop();
    }

    @Override
    public void onPause() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onPause(this);
        }
        super.onPause();
    }

    @Override
    public void onResume() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onResume(this);
        }
        super.onResume();
    }

    @Override
    protected void onDestroy() {
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onDestroy(this);
        }
        super.onDestroy();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (uiExtensionsManager != null) {
            uiExtensionsManager.onConfigurationChanged(this, newConfig);
        }
    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (uiExtensionsManager != null && uiExtensionsManager.onKeyDown(this, keyCode, event))
            return true;
        return super.onKeyDown(keyCode, event);
    }
}

当编译完项目并在模拟器上安装APK后,在弹出的窗口点击”Allow” 允许工程访问设备上的文件。然后您将看到如Figure 3-7所示的界面,点击scan按钮可以开始扫描文档。

Figure 3-7

 

自定义UI

Foxit PDF SDK for Android为开发人员 提供了一个简单、干净和友好的用户界面,可以快速构建一个功能齐全的PDF应用程序而不需花费太多的时间在设计上。此外,自定义用户页面也非常简单。Foxit PDF SDK for Android提供了UI Extensions组件的源代码 (包含即用型UI模块的实现),这样开发人员可以根据需要灵活自定义界面的外观。

从4.0版本开始,开发人员可以通过一个配置文件对功能进行自定义。

从5.0版本开始,内置UI中的任何元素都是可配置的,并且为开发人员提供了更高级的APIs和更强大的配置文件用来进一步自定义UI元素,比如显示或隐藏一个特定的面板,top/bottom toolbar,top/bottom toolbar中的菜单项,以及View setting bar和More Menu View中的菜单项。

从6.3版本开始,配置文件被进一步改进,提供了更多设置选项对UI进行自定义,包括权限管理和UI元素的属性。

以下部分将介绍如何通过配置文件、APIs、源代码自定义功能模块、权限管理和UI元素。

通过配置文件自定义UI

通过配置文件,开发人员可以轻松选择功能模块,设置权限管理和UI元素的属性,而无需编写任何额外的代码或者重新设计应用程序的UI。

JSON文件介绍

配置文件可以作为JSON文件提供,也可以直接在代码中编写。我们建议您使用JSON文件格式,因为其可以更直观,更清晰的查看和配置各个选项。

您可以参考Foxit PDF SDK for Android包中”samples\complete_pdf_viewer\app\src\main\res\raw”文件夹下的JSON文件。其内容如下所示:

{
    "modules": {
        "readingbookmark": true,
        "outline": true,
//        "annotations":true,
        "annotations": {
            "highlight": true,
            "underline": true,
            "squiggly": true,
            "strikeout": true,
            "insert": true,
            "replace": true,
            "line": true,
            "rectangle": true,
            "oval": true,
            "arrow": true,
            "pencil": true,
            "eraser": true,
            "typewriter": true,
            "textbox": true,
            "callout": true,
            "note": true,
            "stamp": true,
            "polygon": true,
            "cloud": true,
            "polyline": true,
            "distance": true,
            "image": true,
            "audio": true,
            "video": true,
            "redaction": true
        },
        "thumbnail": true,
        "attachment": true,
        "signature": true,
        "fillSign": true,
        "search": true,
        "navigation": true,
        "form": true,
        "selection": true,
        "encryption": true
        "multipleSelection": true
    },
    "permissions": {
         "runJavaScript": true,
         "copyText": true,
         "disableLink": false
    },
    "uiSettings": {
      "pageMode": "Single", 
      "continuous": false,
      "reflowBackgroundColor": "#FFFFFF",
      "zoomMode": "FitWidth", 
      "colorMode": "Normal", 
      "mapForegroundColor": "#5d5b71",
      "mapBackgroundColor": "#00001b",
      "disableFormNavigationBar": false,
      "highlightForm": true,
      "highlightFormColor": "#200066cc",
      "highlightLink": true,
      "highlightLinkColor": "#16007fff",
      "fullscreen": true,
      "annotations": {
               "continuouslyAdd": false,
                "highlight": {
                  "color": "#ffff00", "opacity": 1.0               
                },
                "underline": {
                  "color": "#66cc33", "opacity": 1.0
                },
                "squiggly": {
                  "color": "#ff6633", "opacity": 1.0
                },
                "strikeout": {
                  "color": "#ff0000", "opacity": 1.0
                },
                "insert": {
                  "color": "#993399", "opacity": 1.0
                },
                "replace": {
                  "color": "#0000ff", "opacity": 1.0
                },
                "line": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "rectangle": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "oval": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "arrow": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "pencil": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "polygon": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "cloud": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "polyline": {
                  "color": "#ff0000", "opacity": 1.0, "thickness": 2
                },
                "typewriter": {
                  "textColor": "#0000ff",
                  "opacity": 1.0,
                  "textFace": "Courier",
                  "textSize": 18
                },
                "textbox": {
                  "color": "#ff0000",
                  "textColor": "#0000ff",
                  "opacity": 1.0,
                  "textFace": "Courier",
                  "textSize": 18
                },
                "callout": {
                  "color": "#ff0000",
                  "textColor": "#0000ff",
                  "opacity": 1.0,
                  "textFace": "Courier",
                  "textSize": 18
                },
                "note": {
                  "color": "#ff6633",
                  "opacity": 1.0,
                  "icon": "Comment"
                },
                "attachment": {
                  "color": "#ff6633",
                  "opacity": 1.0,
                  "icon": "PushPin"
                },
                "image": {
                   "rotation": 0,
                  "opacity": 1.0
                },
                "distance": {
                   "color": "#ff0000",
                   "opacity": 1.0,
                   "thickness": 2,
                   "scaleFromUnit": "inch", 
                   "scaleToUnit": "inch", 
                   "scaleFromValue": 1,
                   "scaleToValue": 1
                },
                "redaction": {
                   "fillColor": "#000000",
                   "textColor": "#ff0000",
                   "textFace": "Courier",
                   "textSize": 12
                }
        },
      "form": {
        "textField": {
          "textColor": "#000000",
          "textFace": "Courier", // textFace:"Courier"/"Helvetica"/"Times"
          "textSize": 0 // 0 means auto-adjust font size (textSize >= 0)
        },
        "checkBox": {
          "textColor": "#000000"
        },
        "radioButton": {
          "textColor": "#000000"
        },
        "comboBox": {
          "textColor": "#000000",
          "textFace": "Courier", // textFace:"Courier"/"Helvetica"/"Times"
          "textSize": 0, // 0 means auto-adjust font size (textSize >= 0)
          "customText": false
        },
        "listBox": {
          "textColor": "#000000",
          "textFace": "Courier", // textFace:"Courier"/"Helvetica"/"Times"
          "textSize": 0, // 0 means auto-adjust font size (textSize >= 0)
          "multipleSelection": false
        }
      },
      "signature": {
            "color": "#000000", "thickness": 8
      }
    }
}

备注

  • 上述JSON文件中的值是配置项的默认值。如果某些配置项不在JSON文件中,则将使用其默认值。例如,如果注释“highlight”: true,“, 高亮功能仍然是可用的。
  • 只有Annotation工具栏中的附件 (attachment) 注释不受 “annotations”的子项控制的。点击底部Comment可以看到Annotation工具栏然后长按(Note) 看到附件注释,如Figure 4-1所示

“attachment”: true,控制了attachments面板和attachment注释。如果您将其设置为false, 两者都被禁用。如果需要隐藏底部工具栏中的Comment, 那么您需要将annotations attachment同时设置为 false

Figure 4-1

配置项描述

JSON配置文件包括三个部分:功能模块,权限管理和UI设置 (例如,UI元素属性) 。本节将详细介绍这些配置项。

配置功能模块

备注:功能模块项的值类型是bool其中 true表示启用该功能模块,false表示将禁用该功能模块。默认值 true

功能模块 描述
readingbookmark 用户定义书签
outline PDF文档书签
annotations
(highlight, underline, squiggly, strikeout, insert, replace, line, rectangle, oval, arrow, pencil, eraser, typewriter, textbox, callout, note, stamp, polygon, cloud, polyline, distance, image, audio, video, redaction)
注释模块集合
thumbnail PDF页面缩略图显示和页面管理
attachment PDF文档附件和附件注释
signature 电子签名和手写签名
fillSign 用文本和符号填写扁平化表单(即非交互式表单)
search 文本搜索
navigation PDF页面导航
form 表单填写和表单数据导入导出
selection 文本选择
encryption PDF加密
multipleSelection 选择多个annotations

配置权限管理

备注:配置项的类型bool其中 true 表示将启用该权限false表示将禁用该权限runJavaScriptcopyText的默认值 truedisableLink的默认值为 false

权限管理 描述
runJavaScript 是否允许执行JavaScript
copyText 是否允许复制文本
disableLink 是否禁用超链接

配置UI及其属性

UI配置子项 描述/属性 值类型 可选值 默认值 备注
pageMode 页面显示模式 String Single/

Facing/

CoverLeft/

CoverMiddle/

CoverRight/

Reflow

Single 动态XFA文件不支持Reflow模式。
continuous 是否连续的显示单页页面 Bool true/false false True表示连续显示,false表示不连续显示。该配置项在”Reflow”模式下无效。
reflowBack

groundColor

Reflow (重排)页面的背景 RGB #FFFFFF
zoomMode 页面缩放模式 String FitWidth

/FitPage

FitWidth
colorMode 页面颜色显示模式 String Normal/Night

/Map

 

Normal “Night” 是一种特殊的”Map”模式。
mapFore

groundColor

页面显示的前景颜色 RGB #5d5b71 只有在”colorMode”设置为”Map”时,该配置项才有效。
mapBack

groundColor

页面显示的背景颜色 RGB #00001b 只有在”colorMode”设置为”Map”时,该配置项才有效。
disableForm

NavigationBar

是否禁用表单的辅助导航栏 Bool true/false false
highlightForm 是否高亮表单域 Bool true/false true
highlight

FormColor

表单高亮颜色 ARGB #200066cc 包括alpha通道,并且对动态xfa文件无效。
highlightLink 是否高亮超链接 Bool true/false true
highlight

LinkColor

超链接高亮颜色 ARGB #16007fff 包括alpha通道。
fullscreen 是否全屏显示 Bool true/false true 当”fullscreen” 设置为”true”时,文档将以全屏方式显示。如果用户点击页面,工具栏将会出现。如果5秒内无任何动作,工具栏和其他辅助工具按钮将自动隐藏。
 

 

annotations

continuo

uslyAdd

Bool true/false false 是否连续添加某个注释
highlight color RGB #ffff00
opacity numeric [0.0-1.0] 1.0
underline color RGB #66cc33
opacity numeric [0.0-1.0] 1.0
squiggly color RGB #ff6633
opacity numeric [0.0-1.0] 1.0
strikeout color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
insert color RGB #993399
opacity numeric [0.0-1.0] 1.0
replace color RGB #0000ff
opacity numeric [0.0-1.0] 1.0
line color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
rectangle color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
oval color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
arrow color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
pencil color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
polygon color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
cloud color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
polyline color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
typewriter textColor RGB #0000ff
opacity numeric [0.0-1.0] 1.0
textFace String Courier/

Helvetica/

Times

Courier 文本字体名称。

如果设置为非法值,则使用默认字体。

textSize Integer >=1 18
textbox color RGB #ff0000
textColor RGB #0000ff
opacity numeric [0.0-1.0] 1.0
textFace String Courier/

Helvetica/

Times

Courier 文本字体名称。

如果设置为非法值,则使用默认字体。

textSize Integer >=1 18
callout color RGB #ff0000
textColor RGB #0000ff
opacity numeric [0.0-1.0] 1.0
textFace String Courier/

Helvetica/

Times

Courier 文本字体名称。

如果设置为非法值,则使用默认字体。

textSize Integer >=1 18
note color RGB #ff6633
opacity numeric [0.0-1.0] 1.0
icon String Comment/

Key/

Note/

Help/

NewParagraph/

Paragraph/

Insert

Comment 如果设置为非法值,则使用默认值。
attachment color RGB #ff6633
opacity numeric [0.0-1.0] 1.0
icon String Graph/

PushPin/

Paperclip/

Tag

PushPin 如果设置为非法值,则使用默认值。
image rotation numeric 0/90/180

/270

0
opacity numeric [0.0-1.0] 1.0
distance color RGB #ff0000
opacity numeric [0.0-1.0] 1.0
thickness numeric [1-12] 2
scaleFromUnit String pt/m/cm/mm

/inch/p/ft/yd

inch 缩放的基准单位。
scaleToUnit String pt/m/cm/mm

/inch/p/ft/yd

inch 缩放的目标单位。
scaleFromValue numeric 1 缩放的基准数值。
scaleToValue numeric 1 缩放的目标数值。
redaction fillColor RGB #000000
textColor RGB #ff0000
textFace String Courier/

Helvetica/

Times

Courier 文本字体名称。

如果设置为非法值,则使用默认字体。

textSize Integer >=1 12
 

 

form

textField textColor RGB #000000
textFace String Courier/

Helvetica/

Times

Courier 文本字体名称。

如果设置为非法值,则使用默认字体。

textSize Integer >=0 0 0表示自动调整字体大小。
checkBox textColor RGB #000000
radio

Button

textColor RGB #000000
comboBox textColor RGB #000000
textFace String Courier/

Helvetica/

Times

Courier 文本字体名称。

如果设置为非法值,则使用默认字体。

textSize Integer >=0 0 0表示自动调整字体大小。
customText false True表示允许自定义文本。

False表示不允许自定义文本。

listBox textColor RGB #000000
textFace String Courier/

Helvetica/

Times

Courier 文本字体名称。

如果设置为非法值,则使用默认字体。

textSize Integer >=0 0 0表示自动调整字体大小。
multipleSelection false True表示支持多选。

False表示不支持多选。

signature color RGB #000000
thickness numeric [1-12] 8

使用配置文件实例化一个UIExtensionsManager对象

在3.6小节 “使用UI Extensions组件构建一个功能齐全的PDF阅读器“,我们已经介绍了如何实例化UIExtensionsManager,而且使用这种方式,所有的内置UI框架将会被默认加载。在本节中,我们将提供另外一种使用配置文件来实例化一个UIExtensionsManager,以便开发人员可以根据需要轻松自定义UI。

请参阅以下代码使用配置文件实例化UIExtensionsManager对象。

备注这里,我们假设您已经将名为uiextensions_config.jsonJSON文件PDFReader\app\src\main\res\raw文件夹 (注意需要自己创建 raw 文件夹)

在 “MainActivity.java” 中:

import com.foxit.uiextensions.config.Config;
...

private PDFViewCtrl pdfViewCtrl = null;
private UIExtensionsManager uiExtensionsManager = null;
// Initialize a PDFViewCtrl object.
pdfViewCtrl = new PDFViewCtrl(this);

// Get the config file, and set it to UIExtensionsManager.
InputStream stream = this.getApplicationContext().getResources().openRawResource(R.raw.uiextensions_config);
Config config = new Config(stream);

// Initialize a UIExtensionManager object with Configuration file, and set it to PDFViewCtrl.
uiExtensionsManager = new UIExtensionsManager(this.getApplicationContext(), pdfViewCtrl,config);
pdfViewCtrl.setUIExtensionsManager(uiExtensionsManager);
uiExtensionsManager.setAttachedActivity(this);
uiExtensionsManager.onCreate(this, pdfViewCtrl, savedInstanceState);

备注:在上述代码中,我们使用一个配置文件来实例化UIExtensionsManager。如果您不想使用配置文件,可参考3.6小节 使用UI Extensions组件构建一个功能齐全的PDF阅读器

通过配置文件自定义UI的示例

在本节中,我们将向您展示如何在您的项目中自定义功能模块、权限管理和UI设置 (例如,UI元素属性)。您会发现这些自定义都非常容易的实现,您只需要修改配置文件。下面列出了一些操作示例。

备注:为了方便起见,我们 samples文件夹下 complete_pdf_viewer demo进行演示。

在Android Studio中打开 “complete_pdf_viewer” demo。在”complete_pdf_viewer\app\src\main\res\raw” 文件夹下找到配置文件 “uiextensions_config.json”。

示例1禁用“readingbookmark” “navigation” 功能模块。

在JSON文件中,将 “readingbookmark” 和 “navigation” 的值设置为 “false”,如下所示:

"readingbookmark": false,
"navigation": false,

然后,重新编译和运行该demo。如下列出了前后对比图:

修改前:                                                                      修改后:

“readingbookmark”“navigation” 功能模块被移除了。

示例2:禁用超链接。

在JSON文件中,将 “disableLink” 的值设置为 “true”,如下所示:

"permissions": {
     "runJavaScript": true,
     "copyText": true,
     "disableLink": true
},

然后,重新编译和运行该demo,您会发现当您单击超链接时没有任何响应。

示例3:将高亮颜色从黄色设置为红色。

在JSON文件中,将 “highlight” 的color属性设置为 “#ff0000” ,如下所示:

"highlight": {
  "color": "#ff0000", "opacity": 1.0 },

然后,重新编译和运行该demo。如下列出了前后对比图:

修改前:                                                                      修改后:

高亮颜色变为红色了。

通过APIs自定义UI元素

在4.0版本中,Foxit PDF SDK for Android 支持自定义显示或者隐藏整个top toolbar 或者bottom toolbar,从5.0版本开始,提供了APIs去自定义显示或者隐藏一个特定的面板,top/bottom toolbar、View setting bar和More Menu view上面的菜单项,方便开发人员在内置UI框架下对UI元素进行修改。

备注:为了方便起见,我们 samples文件夹下的 complete_pdf_viewer demo中向您展示如何通过APIsUI元素进行自定义。我们假设您没有修改demo中的 uiextensions_config.json 文件,也就是说UI Extensions组件中的所有内置UI都是启用的。

自定义 top/bottom toolbar

对于top/bottom toolbar,您可以执行以下操作:

  1. 显示或者隐藏top/bottom toolbar。
  2. 添加自定义菜单项。
  3. 移除某个特定的菜单项。
  4. 移除toolbar上所有的菜单项。
  5. 显示或者隐藏某个特定的菜单项。
  6. 添加自定义toolbar。
  7. 移除某个特定的toolbar。
  8. 设置toolbar的背景色。
  9. 获取toolbar上特定位置的菜单项个数。

Table 4-1列出了用于自定义top/bottom toolbar相关的APIs。

Table 4-1

void enableTopToolbar(boolean isEnabled) 启用或者禁用top toolbar
void enableBottomToolbar(boolean isEnabled) 启用或者禁用bottom toolbar
boolean addItem(BarName barName, BaseBar.TB_Position gravity, BaseItem item, int index); toolbar上添加自定义菜单项。
boolean addItem(BarName barName, BaseBar.TB_Position gravity, int textId, int resId, int index, IItemClickListener clickListener); toolbar上添加默认菜单项
boolean addItem(BarName barName, BaseBar.TB_Position gravity, CharSequence text, int index, IItemClickListener clickListener); toolbar上添加默认的文本菜单项。
boolean addItem(BarName barName, BaseBar.TB_Position gravity, Drawable drawable, int index, IItemClickListener clickListener); toolbar上添加默认的图片菜单项。
IBaseItem getItemByIndex(BarName barName, BaseBar.TB_Position gravity, int index); 通过索引获取菜单项。
void setItemVisibility(BarName barName, BaseBar.TB_Position gravity, int index, int visibility); 设置菜单项视图的状态:可见或者不可见
int getItemVisibility(BarName barName, BaseBar.TB_Position gravity, int index); 返回菜单项视图的状态:可见或者不可见
int getItemsCount(BarName barName, BaseBar.TB_Position gravity); 通过IBarsHandler.BarName BaseBar.TB_Position获取菜单项个数。
boolean removeItem(BarName barName, BaseBar.TB_Position gravity, int index); 移除toolbar上指定索引位置的菜单项。
boolean removeItem(BarName barName, BaseBar.TB_Position gravity, BaseItem item); 移除toolbar上指定索引位置的菜单项。
void removeAllItems(BarName barName); 移除toolbar所有的菜单项。
boolean addCustomToolBar(BarName barName, View view); 通过BarName添加自定义toolbar
boolean removeToolBar(BarName barName); 通过BarName移除toolbar
void setBackgroundColor(BarName barName, int color); 设置toolbar的背景色。
void setBackgroundResource(BarName barName, int resid); 从指定的资源文件中加载背景样式
BaseItem getItem(BarName barName, BaseBar.TB_Position gravity, int tag); 通过tag获取菜单项,如果tag不存在,则返回null

为了更好的定位需要添加新菜单或者移除已有菜单的位置,定义了两个比较重要的枚举类型,如下所示:

enum BarName {
        TOP_BAR,
        BOTTOM_BAR;
    }

enum TB_Position {
        Position_LT,
        Position_CENTER,
        Position_RB;
    }

备注

  1. Top toolbar或者bottom toolbar最多只能放置7个菜单项。
  2. 在top toolbar上添加菜单项,需要将BaseBar.TB_Position设置为Position_LT 或者 Position_RB。在bottom toolbar上添加菜单项,需要将BaseBar.TB_Position设置为Position_CENTER。否则,菜单项之间可能会重叠。
  3. Bottom toolbar是一个部分,而top toolbar分成两个部分,因此toolbar一共有三个部分,每个部分都有单独的index。(见Figure 4-2)
  4. 为了获得最佳的UI显示效果,建议top toolbar的文本字符数不超过15,bottom toolbar不超过8. 如果超出建议的字符数,可能会导致view排版混乱。

Figure 4-2

在下面的示例中,我们将在”samples”文件夹下的 “complete_pdf_viewer” demo中向您展示如何通过APIs对top/bottom toolbar进行自定义。

在Android Studio中打开 “complete_pdf_viewer” demo。将示例代码加入到”PDFReaderFragment.java”文件中 (加在mUiExtensionsManager = new UIExtensionsManager(getActivity().getApplicationContext(), pdfViewerCtrl, config);”之后)。

示例1隐藏整个top toolbar.

mUiExtensionsManager.enableTopToolbar(false);

修改前:

修改后:

示例2隐藏整个bottom toolbar.

mUiExtensionsManager.enableBottomToolbar(false);

修改前:

修改后:

示例3:在top toolbar左侧的第二个位置加入一个菜单项。

BaseItemImpl mTopItem1 = new BaseItemImpl(getContext());
mTopItem1.setImageResource(R.drawable.rd_annot_item_delete_selector);
mTopItem1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        UIToast.getInstance(getActivity()).show("Add an item in the left top toolbar at the second position.");
    }
});
mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_LT, mTopItem1, 1);

重新运行demo后:

示例4:在top toolbar右侧的第一个位置加入一个菜单项。

BaseItemImpl mTopItem2 = new BaseItemImpl(getContext());
mTopItem2.setImageResource(R.drawable.annot_fileattachment_selector);
mTopItem2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        UIToast.getInstance(getActivity()).show("Add an item in the right top toolbar at the first position");
    }
});
mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_RB, mTopItem2, 0);

重新运行demo后:

示例5:在bottom toolbar的最左边加入一个菜单项。

mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.BOTTOM_BAR, BaseBar.TB_Position.Position_CENTER,
        R.string.fx_more_menu_title, R.drawable.rd_bar_more_selector, 0, new IBarsHandler.IItemClickListener() {
            @Override
            public void onClick(View v) {
                UIToast.getInstance(getActivity()).show("Add an item to the bottom toolbar at the first position.");
            }
        });

重新运行demo后:

示例6:在bottom toolbar的第二个位置加入一个自定义样式的菜单项。

int textSize = getResources().getDimensionPixelSize(R.dimen.ux_text_height_toolbar);
int textColorResId = R.color.ux_text_color_button_colour;
BaseItemImpl mSettingBtn = new BaseItemImpl(this.getContext());
mSettingBtn.setImageResource(R.drawable.rd_annot_create_ok_selector);
mSettingBtn.setText("style");
mSettingBtn.setRelation(BaseItemImpl.RELATION_BELOW);
mSettingBtn.setTextSize(AppDisplay.getInstance(getContext()).px2dp(textSize));
mSettingBtn.setTextColorResource(textColorResId);
mSettingBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        UIToast.getInstance(getActivity()).show("Add an item with custom style to the bottom toolbar at the second position.");
    }
});
mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.BOTTOM_BAR, BaseBar.TB_Position.Position_CENTER, mSettingBtn, 1);

重新运行demo后:

示例7通过索引值移除一个菜单项 (移除bottom toolbar上的第一个菜单项)

mUiExtensionsManager.getBarManager().removeItem(IBarsHandler.BarName.BOTTOM_BAR, BaseBar.TB_Position.Position_CENTER,0);

重新运行demo后:

示例8通过BaseItem对象移除一个菜单项 (top toolbar移除您刚添加的自定义菜单项)

mUiExtensionsManager.getBarManager().removeItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_LT, mTopItem1);

修改前: (见示例3)

修改后:

示例9移除bottom toolbar上所有的菜单项。

mUiExtensionsManager.getBarManager().removeAllItems(IBarsHandler.BarName.BOTTOM_BAR);

修改前:

修改后:

示例10top toolbar左侧添加2菜单项控制more menu菜单项的显示和隐藏。

// Get and save the item that you want to show or hide.
BaseBarManager baseBarManager = (BaseBarManager) mUiExtensionsManager.getBarManager();
final BaseItemImpl moreItem = (BaseItemImpl) baseBarManager.getItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_RB, ToolbarItemConfig.ITEM_TOPBAR_MORE);

// Add a buttom in the left top toolbar to hide the "moreItem" item.
BaseItemImpl mTopItem = new BaseItemImpl(getContext());
mTopItem.setImageResource(R.drawable.rd_annot_item_delete_selector);
mTopItem.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Hide the "moreItem" item.
        mUiExtensionsManager.getBarManager().removeItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_RB, moreItem);
    }
});
mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_LT, mTopItem, 1);

// Add a buttom in the left top toolbar to show the "moreItem" item.
BaseItemImpl mTopItem2 = new BaseItemImpl(getContext());
mTopItem2.setImageResource(R.drawable.annot_reply_item_add_selector);
mTopItem2.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // Show the "moreItem" item.
        mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_RB, moreItem ,2);
    }
});
mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_LT, mTopItem2, 2);

重新运行demo后,top toolbar如下图所示:

点击,则 “more menu” 会被隐藏,如下图所示:

点击,则 “more menu” 会显示,如下图所示:

示例11: 移除整个bottom toolbar

mUiExtensionsManager.getBarManager().removeToolBar(IBarsHandler.BarName.BOTTOM_BAR);

示例12: 添加一个自定义toolbar(添加一个自定义布局文件 test_top_layout)

View topView = View.inflate(getContext(), R.layout.test_top_layout, null);
mUiExtensionsManager.getBarManager().addCustomToolBar(IBarsHandler.BarName.TOP_BAR, topView);

自定义显示/隐藏一个特定的面板

显示或者隐藏一个特定的面板 (见Figure 4-3,包括了”Reading Bookmarks”、”Outline”、”Annotations”、”Attachments” 和 “Digital Signatures” 面板,在首页的bottom toolbar上点击List查看),您可以使用Table 4-2中列出的APIs。

Table 4-2

public void setPanelHidden(boolean isHidden, PanelSpec.PanelType panelType) 根据PanelType显示或者隐藏面板。
public boolean isHiddenPanel(PanelSpec.PanelType panelType) 返回setPanelHidden函数的返回值。

Figure 4-3

备注:通过APIs显示或者隐藏某个特定的面板,请您确保其对应的功能在配置文件中是设置为true否则API的设置不会有任何效果。

在本节中,我们在 “samples” 文件夹下的 “complete_pdf_viewer” demo中提供了一个示例代码用来展示如何通过APIs来显示或者隐藏面板。以”Outline”面板为例,对于其他的面板,您只需要修改PanelType的值。Panels和PanelType之间的对应关系如下表所示:

Panel PanelType
Reading Bookmarks PanelSpec.PanelType.ReadingBookmarks
Outline PanelSpec.PanelType.Outline
Annotations PanelSpec.PanelType.Annotations
Attachments PanelSpec.PanelType.Attachments
Digital Signatures PanelSpec.PanelType.Signatures

在Android Studio中打开 “complete_pdf_viewer” demo。将示例代码加入到”PDFReaderFragment.java”文件中 (加在 mUiExtensionsManager = new UIExtensionsManager(getActivity().getApplicationContext(), pdfViewerCtrl, config);”之后)。

示例:top toolbar左侧的第二个位置添加一个菜单项用来控制是否显示隐藏 Outline 面板

BaseItemImpl mTopItem = new BaseItemImpl(getActivity());
mTopItem.setImageResource(R.drawable.rd_annot_item_delete_selector);
mTopItem.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (mUiExtensionsManager.isHiddenPanel(PanelSpec.PanelType.Outline)){
            mUiExtensionsManager.setPanelHidden(false, PanelSpec.PanelType.Outline);
            UIToast.getInstance(getActivity()).show("show Outline");
        } else {
            mUiExtensionsManager.setPanelHidden(true, PanelSpec.PanelType.Outline);
            UIToast.getInstance(getActivity()).show("hide outline");
        }
    }
});
mUiExtensionsManager.getBarManager().addItem(IBarsHandler.BarName.TOP_BAR, BaseBar.TB_Position.Position_LT, mTopItem, 1);

在这里,我们在top toolbar上添加一个button用来测试该功能。点击button,如果 “Outline”面板存在,则会隐藏它,反之则显示它。

运行demo后,点击 “delete” 按钮,则会弹出 “hide outline”,如下图所示:

然后,在bottom toolbar上点击List,您将看到 “Outline” 面板被隐藏了(见Figure 4-4)。

Figure 4-4

对于Reading Bookmarks、Annotations 、Attachments 和Digital Signatures面板,您只需要修改PanelType 即可。

自定义显示/隐藏View setting bar上的UI元素

显示或者隐藏View setting bar上的UI元素 (见Figure 4-5,在首页的bottom toolbar上点击View查看),您只需要使用下面的API:

public void setVisibility(int type, int visibility)

Figure 4-5

参数 “type” 的值可以参考如下的表格,其对应View setting bar上的菜单项。

type integer
IMultiLineBar.TYPE_LIGHT 1
IMultiLineBar.TYPE_DAYNIGHT 2
IMultiLineBar.TYPE_SYSLIGHT 4
IMultiLineBar.TYPE_SINGLEPAGE 8
IMultiLineBar.TYPE_CONTINUOUSPAGE 16
IMultiLineBar.TYPE_THUMBNAIL 32
IMultiLineBar.TYPE_LOCKSCREEN 64
IMultiLineBar.TYPE_REFLOW 128
IMultiLineBar.TYPE_CROP 256
IMultiLineBar.TYPE_FACINGPAGE 288
IMultiLineBar.TYPE_COVERPAGE 320
IMultiLineBar.TYPE_PANZOOM 384
IMultiLineBar.TYPE_FITPAGE 512
IMultiLineBar.TYPE_FITWIDTH 544
IMultiLineBar.TYPE_ROTATEVIEW 576
IMultiLineBar.TYPE_TTS 640

参数 “visibility” 的值可以参考如下的表格:

visibility integer description
View.VISIBLE 0 The view is visible.
View.GONE/ View.INVISIBLE 8/4 This view is invisible, and it doesn’t take any space for layout.

在本节中,我们在 “samples” 文件夹下的 “complete_pdf_viewer” demo中以 “Reflow” 菜单为例展示如何通过APIs来显示或者隐藏View setting bar上的UI元素。对于其他的UI元素,您只需要修改参数 “type“。

在Android Studio中打开 “complete_pdf_viewer” demo。将示例代码加入到”PDFReaderFragment.java”文件中 (加在mUiExtensionsManager = new UIExtensionsManager(getActivity().getApplicationContext(), pdfViewerCtrl, config);”之后)。

示例1:隐藏View setting bar上面Reflow菜单,但仍然占用布局空间。

mUiExtensionsManager.getSettingBar().setVisibility(IMultiLineBar.TYPE_REFLOW, View.INVISIBLE);

修改前:                                                                      修改后:

对于View setting bar上的其他菜单,您可以参考上述的示例,只需要修改setVisibility接口的”type ” 参数。

显示View setting bar上的任何UI元素,只需要将setVisibility接口的 “visibility“参数的值设置为 “View.VISIBLE“。

自定义显示/隐藏More Menu菜单上的UI元素

显示或者隐藏More Menu 菜单, 请参考4.2.1小节 “自定义 top/bottom toolbar” (见示例10)

显示或者隐藏More Menu View上的UI元素 (见Figure 4-6,在首页右上角点击查看),您可以使用Table 4-3中列出的APIs:

Table 4-3

void setGroupVisibility(int visibility, int tag); 根据tag设置组的可见状态。
void setItemVisibility(int visibility, int groupTag, int itemTag); 根据groupTagitemTag设置子菜单项的可见状态。

Figure 4-6

setGroupVisibility接口中的”tag“参数,以及setItemVisibility接口中的”groupTag“参数可以设置如下所示:

tag integer
GROUP_FILE 100
GROUP_PROTECT 101
GROUP_ANNOTATION 102
GROUP_FORM 103

setItemVisibility接口中的”itemTag“参数可以设置如下所示:

groupTag itemTag integer
GROUP_FILE ITEM_DOCINFO 0
ITEM_SAVE_AS 1
ITEM_REDUCE_FILE_SIZE 2
ITEM_PRINT_FILE 3
ITEM_SNAPSHOT 4
GROUP_PROTECT ITEM_PASSWORD 0
ITEM_REMOVESECURITY_PASSWORD 4
ITEM_TRUST_CERTIFICATE 9
GROUP_ANNOTATION ITEM_ANNOTATION_IMPORT 0
ITEM_ANNOTATION_EXPORT 1
GROUP_FORM ITEM_CREATE_FORM 0
ITEM_RESET_FORM 1
ITEM_IMPORT_FORM 2
ITEM_EXPORT_FORM 3

setGroupVisibilitysetItemVisibility接口中的”visibility“参数可以设置如下所示:

visibility integer description
View.VISIBLE 0 The view is visible.
View.INVISIBLE 4 This view is invisible, but it still takes up space for layout purposes.
View.GONE 8 This view is invisible, and it doesn’t take any space for layout.

备注:对于setItemVisibility接口,如果您需要显示或者隐藏某个itemTag单项,请确保其对应的groupTag是设置为View.VISIBLE。否则,设置itemTag的显示或隐藏设置将不会有任何效果。

在本节中,我们在 “samples” 文件夹下的 “complete_pdf_viewer” demo中以 “GROUP_FILE” (File) 和”ITEM_DOCINFO” (Properties) 为例展示如何通过APIs来显示或者隐藏More Menu view上的UI元素。对于其他的UI元素,您可以参考下面的示例,只需要修改setGroupVisibilitysetItemVisibility接口中的参数值。

在Android Studio中打开 “complete_pdf_viewer” demo。将示例代码加入到”PDFReaderFragment.java”文件中 (加在mUiExtensionsManager = new UIExtensionsManager(getActivity().getApplicationContext(), pdfViewerCtrl, config);”之后)。

示例1隐藏More Menu view上面File菜单,但仍然占用布局空间。

// Get MenuViewImpl from MoreMenuModule.
MoreMenuModule moreMenuModule = (MoreMenuModule) mUiExtensionsManager.getModuleByName(Module.MODULE_MORE_MENU);
MenuViewImpl menuView = (MenuViewImpl) moreMenuModule.getMenuView();

menuView.setGroupVisibility(View.INVISIBLE, MoreMenuConfig.GROUP_FILE);

修改前:                                                                      修改后:

 

示例2隐藏More Menu view上面File菜单组,并且不会占用布局空间。

// Get MenuViewImpl from MoreMenuModule.
MoreMenuModule moreMenuModule = (MoreMenuModule) mUiExtensionsManager.getModuleByName(Module.MODULE_MORE_MENU);
MenuViewImpl menuView = (MenuViewImpl) moreMenuModule.getMenuView();

menuView.setGroupVisibility(View.GONE, MoreMenuConfig.GROUP_FILE);

修改前:                                                                      修改后:

 

示例3隐藏More Menu view上面Properties菜单,但仍然占用布局空间。

// Get MenuViewImpl from MoreMenuModule.
MoreMenuModule moreMenuModule = (MoreMenuModule) mUiExtensionsManager.getModuleByName(Module.MODULE_MORE_MENU);
MenuViewImpl menuView = (MenuViewImpl) moreMenuModule.getMenuView();

menuView.setItemVisibility(View.INVISIBLE, MoreMenuConfig.GROUP_FILE, MoreMenuConfig.ITEM_DOCINFO);

修改前:                                                                      修改后:

 

示例4隐藏More Menu view上面Properties菜单并且不会占用布局空间。

// Get MenuViewImpl from MoreMenuModule.
MoreMenuModule moreMenuModule = (MoreMenuModule) mUiExtensionsManager.getModuleByName(Module.MODULE_MORE_MENU);
MenuViewImpl menuView = (MenuViewImpl) moreMenuModule.getMenuView();

menuView.setItemVisibility(View.GONE, MoreMenuConfig.GROUP_FILE, MoreMenuConfig.ITEM_DOCINFO);

修改前:                                                                      修改后:

对于More Menu view上面的其他菜单项,您可以参考上述的示例,只需要修改setGroupVisibilitysetItemVisibility接口的参数值即可。

显示More Menu view上的UI元素,只需要将setGroupVisibilitysetItemVisibility接口的 “visibility“参数的值设置为 “View.VISIBLE“即可。

通过源代码自定义UI实现

在前面的章节中,我们详细地介绍了如何通过配置文件或者APIs对UI进行自定义。这些修改是基于Foxit PDF SDK for Android的内置UI框架的。如果您不想使用当前现成的UI框架,您可以通过修改UI Extensions组件中的源代码来重新设计UI。

为了自定义UI实现,您可以按照下面的步骤:

备注本节中,我们只介绍如何自定义UI Extensions组件UI,有关自定义扫描功能的UI,您可以参考本节的教程。

首先,在您的工程中添加如下的文件,这些文件都在包中的”libs”文件夹下。

  • uiextensions_src 工程 – 一个开源库,包含了一些即用型的UI模块实现,可以帮助开发人员快速将功能齐全的PDF阅读器嵌入到他们的Android应用中。当然,开发人员也不是必须要使用默认的UI,可以通过”uiextensions_src”工程为特定的应用灵活自定义和设计UI。
  • FoxitRDK.aar – 包含JAR包,其中包括Foxit PDF SDK for Android的所有Java APIs,以及”.so”库。”.so”库是SDK的核心包含了Foxit PDF SDK for Android的核心函数。它针对每种架构单独编译,当期支持armeabi-v7a, arm64-v8a, x86, 和 x86_64架构。

备注uiextensions_src工程依赖于FoxitRDK.aar,因此最好将它们放在一个目录下。如果不是,您需要在uiextensions_src工程的build.gradle文件中手动修改FoxitRDK.aar的引用路径。

其次,在uiextensions_src工程中定位到您需要自定义的XML布局文件,然后根据您的需求进行修改。

为方便起见,我们将在”sample”文件夹下的”viewer_ctrl_demo” 中向您展示如何自定义UI实现。

UI Customization Example UI自定义示例

步骤1向 “viewer_ctrl_demo” 中添加uiextensions_src工程,请确保其与FoxitRDK.aar文件在一个目录下。如果您没有修改默认文件夹的层结构,那么它们就是在正确的位置。

备注viewer_ctrl_demo已经添加了FoxitRDK.aar的引用,因此我们只需要通过配置“settings.gradle”文件来添加uiextensions_src工程。当添加uiextensions_src作为依赖后,需要将FoxitRDKUIExtensions.aar的引用移除。

在Android Studio中加载 “viewer_ctrl_demo“。然后按照下面的步骤:

a)在 “settings.gradle” 文件中,添加uiextensions_src工程,代码如下。

settings.gradle:

       
include ':app'
include ':uiextensions_src'
project(':uiextensions_src').projectDir = new File('../../libs/uiextensions_src/')

重新编译gradle,则uiextensions_src工程会被添加到demo中,如Figure 4-7所示。

Figure 4-7

b)在demo中将uiextensions_src工程作为demo的依赖项。在app的 “build.gradle” 文件中,添加implementation project(“:uiextensions_src”) ,然后注释掉implementation(name:’FoxitRDKUIExtensions’, ext:’aar’),如下所示:

dependencies {
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.0.0'
    implementation 'androidx.multidex:multidex:2.0.1'
    implementation (name: 'FoxitRDK', ext: 'aar')
   // implementation(name:'FoxitRDKUIExtensions', ext:'aar')
    implementation project(":uiextensions_src")
    implementation 'com.edmodo:cropper:1.0.1'
    implementation('com.microsoft.aad:adal:1.16.3') {}
    implementation(name: 'RMSSDK-4.2-release', ext: 'aar')
    implementation(name: 'rms-sdk-ui', ext: 'aar')
    implementation 'org.bouncycastle:bcpkix-jdk15on:1.64'
    implementation 'org.bouncycastle:bcprov-jdk15on:1.64'
}

当进行更改后,重新编译gradle。然后,选择 “File -> Project Structure…” 打开Project Structure对话框。在对话框中点击 “Modules -> app“,选择Dependencies选项,您可以看到该demo依赖于uiextensions_src工程,如Figure 4-8。

Figure 4-8

恭喜您!您已经完成了第一步。

步骤2查找和修改与您需要自定义的UI相关的布局文件。

现在,我们将向您展示一个简单的示例,在搜索面板中修改button的图标,如Figure 4-9所示。

Figure 4-9

要替换图标,我们只需要找到存储该button图标的位置,然后使用另一个具有相同名称的图标来替换它。

在工程中,点击 “uiextensions_src -> src -> main -> res -> layout“,如Figure 4-10所示。

Figure 4-10

在布局文件列表中,找到 “search_layout.xml ” 文件,然后双击它。在Preview窗口中找到该按钮,然后通过单击它来定位到其相关代码,如Figure 4-11所示。

Figure 4-11

完成上图中的三个步骤后,跳转到 “search_result_selector.xml“,如Figure 4-12所示。通过Ctrl+左键单击 “search_result”,可以看到该button的图标存储在 “drawable-xxx” 文件夹中,图标名称为 “search_result.png”。只需将其替换为您自己的图标即可。

备注Foxit PDF SDK for Android为具有不同DPI要求的设备提供了三组图标,以确保您的应用程序在每台设备上可以流畅运行

Figure 412

例如,我们使用search next的图标 (“search_next.png” 与 “search_result.png” 在相同的文件夹下) 来替换它。然后重新运行demo,使用搜索功能,可以看到底部搜索button的图标已更改,如Figure 4-13所示。

Figure 4-13

这只是一个简单的示例用来展示如何自定义UI实现。您可以作为参考,通过uiextensions_src工程您可以自由的对特定的应用程序进行UI自定义和设计。

使用SDK API

Foxit PDF SDK for Android将所有功能实现封装在UI Extensions 组件中。如果您对功能实现的详细过程感兴趣,请参考本节内容。

在本节中,我们将介绍Foxit PDF SDK for Android的主要功能,并列举相关示例来展示如何使用Foxit PDF SDK Core API实现这些功能。

Render

PDF渲染是通过Foxit渲染引擎实现的,Foxit渲染引擎是一个图形引擎,用于将页面渲染到位图或平台设备上下文。 Foxit PDF SDK提供了APIs用来设置渲染选项/flags,例如设置 flag来决定是否渲染表单域和签名,是否绘制图像反锯齿 (anti-aliasing) 和路径反锯齿。可以使用以下APIs进行渲染:

  • 渲染页面和注释时,首先使用Renderer.setRenderContentFlags接口来决定是否同时渲染页面和注释,然后使用Renderer.startRender接口进行渲染。Renderer.startQuickRender接口也可以用来渲染页面但仅用于缩略图。
  • 渲染单个annotation注释,使用Renderer.renderAnnot接口。
  • 在位图上渲染,使用Renderer.startRenderBitmap接口。
  • 渲染一个重排的页面,使用 Renderer.startRenderReflowPage接口。

在Foxit PDF SDK中,Widget注释常与表单域和表单控件相关联。渲染widget注释,推荐使用如下的流程:

  • 加载PDF页面后,首先渲染页面以及该页面上所有的注释 (包括widget注释)。
  • 然后,如果使用pdf.interform.Filler对象来填表则应使用pdf.interform.Filler.render接口来渲染当前获取到焦点的表单控件而不是使用Renderer.renderAnnot接口。

Example:

如何将指定的PDF页面渲染到bitmap

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Progressive;
import com.foxit.sdk.common.Renderer;
import com.foxit.sdk.common.fxcrt.Matrix2D;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.PDFPage;
...



public Bitmap renderPageToBitmap(PDFPage pdfPage, int drawPageWidth, int drawPageHeight) {
    try {
        // If the page hasn't been parsed yet, throw an exception.
        if (pdfPage.isParsed()) {
            throw new PDFException(Constants.e_ErrNotParsed, "PDF Page should be parsed first");
        }

        // Pepare matrix to render on the bitmap.
        Matrix2D matrix2D = pdfPage.getDisplayMatrix(0, 0, drawPageWidth, drawPageHeight, Constants.e_Rotation0);
        // Create a bitmap according to the required drawPageWidth and drawPageHeight.
        Bitmap bitmap = Bitmap.createBitmap(drawPageWidth, drawPageHeight, Bitmap.Config.RGB_565);
        // Fill the bitmap with white color.
        bitmap.eraseColor(Color.WHITE);
        Renderer renderer = new Renderer(bitmap, true);
        // Set the render flag, both page content and annotation will be rendered.
        renderer.setRenderContentFlags(Renderer.e_RenderPage | Renderer.e_RenderAnnot);
        // Start to render the page progressively.
        Progressive progressive = renderer.startRender(pdfPage, matrix2D, null);
        int state = Progressive.e_ToBeContinued;
        while (state == Progressive.e_ToBeContinued) {
            state = progressive.resume();
        }

        if (state != Progressive.e_Finished) return null;
        return bitmap;
    } catch (PDFException e) {
        e.printStackTrace();
    }
    return null;
}

Text Page

Foxit PDF SDK提供APIs来提取,选择,搜索和检索PDF文档中的文本。 PDF文本内容存储在与特定页面相关的TextPage对象中。 TextPage类可用于检索PDF页面中文本的信息,例如单个字符,单个单词,指定字符范围或矩形内的文本内容等。它还可用于构造其他文本相关类的对象,用来对文本内容执行更多操作或从文本内容访问指定信息:

  • 在PDF页面的文本内容中搜索文本,使用TextPage对象来构建TextSearch对象。
  • 访问类似超文本链接的文本,使用TextPage对象来构建PageTextLinks对象。
  • 高亮PDF页面上的选中文本,构建一个TextPage对象来计算选中文本区域。

Example:

如何通过选择获取页面上的文本区域

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.pdf.TextPage;
import com.foxit.sdk.common.fxcrt.PointF;
...

// Get the text area on page by selection. The starting selection position and ending selection position are specified by startPos and endPos.
public ArrayList<RectF> getTextRectsBySelection(PDFPage page, PointF startPos, PointF endPos) {
    try {
        // If the page hasn't been parsed yet, throw an exception.
        if (page.isParsed()) {
            throw new PDFException(Constants.e_ErrNotParsed, "PDF Page should be parsed first");
        }

        // Create a text page from the parsed PDF page.
        TextPage textPage = new TextPage(page, TextPage.e_ParseTextNormal);
        if (textPage == null || textPage.isEmpty()) return null;
        int startCharIndex = textPage.getIndexAtPos(startPos.getX(), startPos.getY(), 5);
        int endCharIndex = textPage.getIndexAtPos(endPos.getX(), endPos.getY(), 5);
        // API getTextRectCount requires that start character index must be lower than or equal to end character index.
        startCharIndex = startCharIndex < endCharIndex ? startCharIndex : endCharIndex;
        endCharIndex = endCharIndex > startCharIndex ? endCharIndex : startCharIndex;
        int count = textPage.getTextRectCount(startCharIndex, endCharIndex - startCharIndex);

        if (count > 0) {
            ArrayList<RectF> array = new ArrayList<RectF>();
            for (int i = 0; i < count; i ++) {
                RectF rectF = textPage.getTextRect(i);
                if (rectF == null || rectF.isEmpty()) continue;
                array.add(rectF);
            }
            // The return rects are in PDF unit, if caller need to highlight the text rects on the screen, then these rects should be converted in device unit first.
            return array;
        }
    } catch (PDFException e) {
        e.printStackTrace();
    }
    return null;
}
...

Foxit PDF SDK 提供APIs来搜索PDF文档、XFA文档、文本页面或者PDF注释中的文本。它提供了文本搜索和获取搜索结果的函数:

  • 指定搜索模式和选项,使用TextSearch.setPatternTextSearch.setStartPage (仅对PDF文档中的文本搜索有用)TextSearch.setEndPage (仅对PDF文档中的文本搜索有用)TextSearch.setSearchFlags接口。
  • 进行搜索,使用TextSearch.findNextTextSearch.findPrev接口。
  • 获取搜索结果,使用TextSearch.getMatchXXX() 接口。

Example:

如何在PDF文档中搜索指定的文本模型

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.common.fxcrt.RectFArray;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.pdf.TextPage;
import com.foxit.sdk.common.fxcrt.PointF;
import com.foxit.sdk.pdf.TextSearch;
...

try {
    String pdfpath = "XXX/Sample.pdf";
    PDFDoc doc = new PDFDoc(pdfpath);


    doc.load(null);

    // Create a text search handler for searching in PDF document.
    TextSearch textSearch = new TextSearch(doc, null,Textpage.e_ParseTextNormal););
    // Set the start page index which searching will begin. By default, end page will be the last page.
    textSearch.setStartPage(0);
    // Set the text to be searched.
    textSearch.setPattern("foxit");
    // Set the search flags to be matching case and matching whole word.
    textSearch.setSearchFlags(TextSearch.e_SearchMatchCase|TextSearch.e_SearchMatchWholeWord);
    while (textSearch.findNext()) {
        // If true, then we found a matched result.
        // Get the found page index.
        int pageIndx = textSearch.getMatchPageIndex();
        // Get the start character index of the matched text on the found page.
        int startCharIndex = textSearch.getMatchStartCharIndex();
        // Get the end character index of the matched text on the found page.
        int endCharIndex = textSearch.getMatchEndCharIndex();
        // Get the rectangular region of the matched text on the found page.
        RectFArray matchRects = textSearch.getMatchRects();
    }
}
catch (Exception e) {}
...

Bookmark (Outline)

Foxit PDF SDK提供了名为Bookmarks的导航工具,允许用户在PDF文档中快速定位和链接他们感兴趣的部分。PDF书签也称为outline,每个书签包含一个目标位置或动作来描述它链接到的位置。它是一个树形的层次结构,因此在访问bookmark 树之前,必须首先调用接口PDFDoc.getRootBookmark以获取整个bookmark树的root。这里,”root bookmark”是一个抽象对象,它只有一些child bookmarks,没有next sibling bookmarks, 也没有任何数据 (包括bookmark数据,目标位置数据和动作数据)。因为它没有任何数据,因此无法在应用程序界面上显示,能够调用的接口只有 Bookmark.getFirstChild

在检索root bookmark后,就可以调用以下的接口去访问其他的bookmarks:

  • 访问parent bookmark,使用Bookmark.getParent接口。
  • 访问第一个child bookmark,使用Bookmark.getFirstChild接口。
  • 访问next sibling bookmark,使用Bookmark.getNextSibling接口。
  • 插入一个新的bookmark,使用Bookmark.insert接口。
  • 移动一个bookmark,使用Bookmark.moveTo接口。

Example:

如何使用深度优先顺序遍历PDF文档的bookmarks

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.pdf.Bookmark;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.actions.Destination;
...
private void DepthFistTravelBookmarkTree(Bookmark bookmark, PDFDoc doc) {
    if(bookmark == null || bookmark.isEmpty())
        return;
    try {
        DepthFistTravelBookmarkTree(bookmark.getFirstChild(), doc);
        while (true) {
            // Get bookmark title.
            String title = bookmark.getTitle();
            Destination dest = bookmark.getDestination();
            if (dest != null && !dest.isEmpty())
            {
                float left, right, top, bottom;
                float zoom;
                int pageIndex = dest.getPageIndex(doc);
                // left,right,top,bottom,zoom are only meaningful with some special zoom modes.
                int mode = dest.getZoomMode();
                switch (mode) {
                    case Destination.e_ZoomXYZ:
                        left = dest.getLeft();
                        top = dest.getTop();
                        zoom = dest.getZoomFactor();
                        break;
                    case Destination.e_ZoomFitPage:
                        break;
                    case Destination.e_ZoomFitHorz:
                        top = dest.getTop();
                        break;
                    case Destination.e_ZoomFitVert:
                        left = dest.getLeft();
                        break;
                    case Destination.e_ZoomFitRect:
                        left = dest.getLeft();
                        bottom = dest.getBottom();
                        right = dest.getRight();
                        top = dest.getTop();
                        break;
                    case Destination.e_ZoomFitBBox:
                        break;
                    case Destination.e_ZoomFitBHorz:
                        top = dest.getTop();
                        break;
                    case Destination.e_ZoomFitBVert:
                        left = dest.getLeft();
                        break;
                    default:
                        break;
                }
            }
            bookmark = bookmark.getNextSibling();
            if (bookmark == null || bookmark.isEmpty())
                break;
            DepthFistTravelBookmarkTree(bookmark.getFirstChild(), doc);
        }
    }
    catch (Exception e) {
    }
}

Reading Bookmark

Reading bookmark不是PDF bookmark,换句话说,它不是PDF outlines。Reading bookmark是应用层的书签。它存储在目录的元数据(XML格式)中,允许用户根据他们的阅读偏好添加或删除reading bookmark,并通过选择reading bookmark可以轻松导航到一个PDF页面。

为了检索reading bookmark,可以调用PDFDoc.getReadingBookmarkCount接口来计算其个数,并且可以调用PDFDoc.getReadingBookmark接口以索引方式获取相应的reading bookmark。

此类提供了接口用来获取/设置reading bookmarks属性,比如标题,目标页面索引,以及创建/修改日期时间。

如何添加自定义reading bookmark并枚举所有的reading bookmarks

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.DateTime;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.ReadingBookmark;
...
// Add a new reading bookmark to pdf document, the returned bookmark stores the title and the page index.
ReadingBookmark addReadingBookmark(PDFDoc pdfDoc, String title, int pageIndex) {
    int count = pdfDoc.getReadingBookmarkCount();
    return pdfDoc.insertReadingBookmark(count,title,pageIndex);
}

// Enumerate all the reading bookmarks from the pdf document.
void getReadingBookmark(PDFDoc pdfDoc) {
    try {
        int count = pdfDoc.getReadingBookmarkCount();
        for (int i = 0; i < count; i ++) {
            ReadingBookmark readingBookmark = pdfDoc.getReadingBookmark(i);
            if(readingBookmark.isEmpty()) continue;
            // Get bookmark title.
            String title = readingBookmark.getTitle();
            // Get the page index which associated with the bookmark.
            int pageIndex = readingBookmark.getPageIndex();
            // Get the creation date of the bookmark.
            DateTime creationTime = readingBookmark.getDateTime(true);
            // Get the modification date of the bookmark.
            DateTime modificationTime = readingBookmark.getDateTime(false);
        }
    } catch (PDFException e) {
        e.printStackTrace();
    }
}

Attachment

在Foxit PDF SDK中,attachments指的是文档附件而不是文件附件注释。它允许将整个文件封装在文档中,就像电子邮件附件一样。Foxit PDF SDK提供应用程序APIs来访问附件,例如加载附件,获取附件,插入/删除附件,以及访问附件的属性。

Example:

如何将指定文件嵌入到PDF文档

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.pdf.Attachments;
import com.foxit.sdk.pdf.FileSpec;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.objects.PDFNameTree;
...
try {
    String pdfpath = "XXX/Sample.pdf";
    PDFDoc doc = new PDFDoc(pdfpath);


    doc.load(null);

    // Embed the specified file to PDF document.
    String filePath = "/xxx/fileToBeEmbedded.xxx";
    PDFNameTree nameTree = new PDFNameTree(doc, PDFNameTree.e_EmbeddedFiles);
    Attachments attachments = new Attachments(doc, nameTree);
    FileSpec fileSpec = new FileSpec(doc);
    fileSpec.setFileName(filePath);
    if (!fileSpec.embed(filePath)) return;
    attachments.addEmbeddedFile(filePath, fileSpec);
} catch (PDFException e) {
    e.printStackTrace();
}
...

如何从PDF文档中导出嵌入的附件文件,并将其另存为单个文件

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.pdf.Attachments;
import com.foxit.sdk.pdf.FileSpec;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.objects.PDFNameTree;
...
try {
    String pdfpath = "XXX/Sample.pdf";
    PDFDoc doc = new PDFDoc(pdfpath);


    doc.load(null);

    PDFNameTree nameTree = new PDFNameTree(doc, PDFNameTree.e_EmbeddedFiles);
    Attachments attachments = new Attachments(doc, nameTree);
    // Extract the embedded attachment file.
    int count = attachments.getCount();
    for (int i = 0; i < count; i ++) {
        String key = attachments.getKey(i);
        if (key != null) {
            FileSpec fileSpec1 = attachments.getEmbeddedFile(key);
            String exportedFile = "/somewhere/" + fileSpec1.getFileName();
            if (fileSpec1 != null && !fileSpec1.isEmpty()) {
                fileSpec1.exportToFile(exportedFile);
            }
        }
    }
} catch (PDFException e) {
    e.printStackTrace();
}
...

Annotation

一个annotation注释将对象(如注释,线条和高亮)与PDF文档页面上的位置相关联。 PDF包括如Table 5-1中列出的各种标准注释类型。在这些注释类型中,许多被定义为标记注释,因为它们主要用于标记PDF文档。Table 5-1中的 “Markup” 列用来说明是否为标记注释。

Foxit PDF SDK支持PDF Reference中定义的大多数注释类型。Foxit PDF SDK提供了注释创建,属性访问和修改,外观设置和绘制的APIs。

Table 5-1

Annotation type Description Markup Supported by SDK
Text(Note) Text annotation Yes Yes
Link Link Annotation No Yes
FreeText
(TypeWriter/TextBox/Callout)
Free text annotation Yes Yes
Line Line annotation Yes Yes
Square Square annotation Yes Yes
Circle Circle annotation Yes Yes
Polygon Polygon annotation Yes Yes
PolyLine PolyLine annotation Yes Yes
Highlight Highlight annotation Yes Yes
Underline Underline annotation Yes Yes
Squiggly Squiggly annotation Yes Yes
StrikeOut StrikeOut annotation Yes Yes
Stamp Stamp annotation Yes Yes
Caret Caret annotation Yes Yes
Ink(pencil) Ink annotation Yes Yes
Popup Popup annotation No Yes
File Attachment FileAttachment annotation Yes Yes
Sound Sound annotation Yes No
Movie Movie annotation No No
Widget* Widget annotation No Yes
Screen Screen annotation No Yes
PrinterMark PrinterMark annotation No No
TrapNet Trap network annotation No No
Watermark* Watermark annotation No No
3D 3D annotation No No
Redact Redact annotation Yes Yes

备注: Foxit SDK支持名为PSI (pressure sensitive ink,压感笔迹) 的自定义注释类型。在PDF规范中没有对该注释进行描述。通常,PSI用于手写功能,Foxit SDK将其视为PSI注释,以便其他PDF产品可以对其进行相关处理。

Example:

如何向PDF页面中添加注释

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.common.fxcrt.RectFArray;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.pdf.TextSearch;
import com.foxit.sdk.pdf.annots.Annot;
import com.foxit.sdk.pdf.annots.Note;
import com.foxit.sdk.pdf.annots.QuadPoints;
import com.foxit.sdk.pdf.annots.QuadPointsArray;
import com.foxit.sdk.pdf.annots.TextMarkup;
import com.foxit.sdk.common.fxcrt.PointF;
...
// Add text annot.
try {
    String pdfpath = "xxx/Sample.pdf";
    PDFDoc doc = new PDFDoc(pdfpath);
    doc.load(null);
    PDFPage pdfPage = doc.getPage(1);
    RectF rect = new RectF(100, 100, 120, 120);
    Note note = new Note(pdfPage.addAnnot(Annot.e_Note, rect));
    if (note == null || note.isEmpty()){
        return;
    }
    note.setIconName("Comment");
    // Set color to blue.
    note.setBorderColor(0xff0000ff);
    note.setContent("This is the note comment, write any content here.");
    note.resetAppearanceStream();

    // The following code demonstrates how to add hightlight annotation on the searched text.
    TextSearch textSearch = new TextSearch(pdfPage.getDocument(),null,TextPage.e_ParseTextNormal);
    if (textSearch == null || textSearch.isEmpty()){
        return;
    }
    // Suppose that the text for highlighting is "foxit".
    textSearch.setPattern("foxit");
    boolean bMatched = textSearch.findNext();
    if (bMatched) {
        RectFArray rects = textSearch.getMatchRects();
        int rectCount = rects.getSize();
        // Fill the quadpoints array according to the text rects of matched result.
        QuadPointsArray arrayOfQuadPoints = new QuadPointsArray();
        for (int i = 0; i < rectCount; i++) {
            rect = rects.getAt(i);
            QuadPoints quadPoints = new QuadPoints();
            PointF point = new PointF();
            point.set(rect.getLeft(), rect.getTop());
            quadPoints.setFirst(point);
            point.set(rect.getRight(), rect.getTop());
            quadPoints.setSecond(point);
            point.set(rect.getLeft(), rect.getBottom());
            quadPoints.setThird(point);
            point.set(rect.getRight(), rect.getBottom());
            quadPoints.setFourth(point);
            arrayOfQuadPoints.add(quadPoints);
        }
        // Just set an empty rect to markup annotation, the annotation rect will be calculated according to the quadpoints that set to it later.
        rect = new RectF(0, 0, 0, 0);
        TextMarkup textMarkup = new TextMarkup(pdfPage.addAnnot(Annot.e_Highlight, rect));
        // Set the quadpoints to this markup annot.
        textMarkup.setQuadPoints(arrayOfQuadPoints);
        // set to red.
        textMarkup.setBorderColor(0xffff0000);
        // set to thirty-percent opacity.
        textMarkup.setOpacity(0.3f);
        // Generate the appearance.
        textMarkup.resetAppearanceStream();
    }
} catch (Exception e) {}

如何删除PDF页面中的注释

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.pdf.annots.Annot;
...

try {
    String pdfpath = "xxx/Sample.pdf";
    PDFDoc doc = new PDFDoc(pdfpath);
    doc.load(null);
    PDFPage pdfPage = doc.getPage(1);
    Annot annot = pdfPage.getAnnot(0);
    if (annot == null || annot.isEmpty())
        return;
    // Remove the first annot, so the second annot will become first.
    pdfPage.removeAnnot(annot);
} catch (Exception e) {}

Form

Form(AcroForm)是用于收集用户交互信息的表单域的集合。Foxit PDF SDK提供了以编程方式查看和编辑表单域的APIs。在PDF文档中,表单域通常用于收集数据。 Form类提供了APIs用来检索表单域或表单控件,导入/导出表单数据,以及其他功能,例如:

  • 检索表单域,使用Form.getFieldCount Form.getField接口。
  • 检索PDF页面中的表单控件,使用Form.getControlCountForm.getControl接口。
  • 从XML文件导入表单数据,使用Form.importFromXML接口;导出表单数据到XML文件,使用Form.exportToXML接口。
  • 检索form filler对象,使用Form.getFormFiller接口。

从FDF/XFDF文件中导入表单数据,或者导出数据到FDF/XFDF文件,请参考PDFDoc.importFromFDFPDFDoc.exportToFDF 接口。

Example:

如何通过XML文件导入表单数据或将表单数据导出到XML文件

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.interform.Form;
...

// Check if the document has a form.
try {
    String pdfpath = "xxx/Sample.pdf";
    PDFDoc doc = new PDFDoc(pdfpath);
    doc.load(null);
    boolean hasForm = doc.hasForm();
    if(hasForm) {
        // Create a form object from document.
        Form form = new Form(doc);
        // Export the form data to a XML file.
        form.exportToXML("/somewhere/export.xml");
        // Or import the form data from a XML file.
        form.importFromXML("/somewhere/export.xml");
    }
}catch (Exception e) {}

Security

Foxit PDF SDK提供了一系列加密和解密功能,以满足不同级别的文档安全保护。用户可以使用常规密码加密和证书驱动加密,或使用自己的安全处理机制来自定义安全实现。

Example:

如何使用密码加密PDF文件

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.SecurityHandler;
import com.foxit.sdk.pdf.StdEncryptData;
import com.foxit.sdk.pdf.StdSecurityHandler;
...

// Encrypt the source pdf document with specified owner password and user password, the encrypted PDF will be saved to the path specified by parameter savePath.
public boolean encryPDF(PDFDoc pdfDoc, byte[] ownerPassword, byte[] userPassword, String savePth) {
    if (pdfDoc == null || (ownerPassword == null && userPassword == null) || savePth == null)
        return false;
    // The encryption setting data. Whether to encrypt meta data:true, User permission: modify,assemble,fill form. Cipher algorithm:AES 128.
    StdEncryptData encryptData = new StdEncryptData(true,
            PDFDoc.e_PermModify | PDFDoc.e_PermAssemble | PDFDoc.e_PermFillForm,
            SecurityHandler.e_CipherAES, 16);

    StdSecurityHandler securityHandler = new StdSecurityHandler();
    try {
        if (!securityHandler.initialize(encryptData, userPassword, ownerPassword)) return false;
        pdfDoc.setSecurityHandler(securityHandler);
        if (!pdfDoc.saveAs(savePth, PDFDoc.e_SaveFlagNormal)) return false;
        return true;
    } catch (PDFException e) {
        e.printStackTrace();
    }
    return false;
}

Signature

PDF签名可用于创建和签署PDF文档的数字签名,从而保护文档内容的安全性并避免文档被恶意篡改。它可以让接收者确保其收到的文档是由签名者发送的,并且文档内容是完整和未被经篡的。Foxit PDF SDK提供APIs用来创建数字签名,验证签名的有效性,删除现有的数字签名,获取和设置数字签名的属性,显示签名和自定义签名表单域的外观。

备注Foxit PDF SDK提供了默认签名回调函数,支持如下两种类型的signature filter subfilter:

(1) filter: Adobe.PPKLite                  subfilter: adbe.pkcs7.detached

(2) filter: Adobe.PPKLite                  subfilter: adbe.pkcs7.sha1

如果您使用以上任意一种的signature filter subfilter,您可以直接签名PDF文档和验证签名的有效性而不需要注册自定义回调函数

Example:

如何对PDF文档进行签名,并验证签名

import com.foxit.sdk.PDFException;
import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.common.Constants;
import com.foxit.sdk.common.Progressive;
import com.foxit.sdk.common.fxcrt.RectF;
import com.foxit.sdk.pdf.PDFDoc;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.pdf.Signature;
...

// Sample code demonstrate signing and verifying of PDF signature.
public void addNewSignatureAndSign(PDFPage page, RectF rect) {
    try {
        // Add a new signature on the specified page rect.
        Signature signature = page.addSignature(rect);
        // Set the appearance flags, if the specified flag is on, then the associated key will be displayed on the signature appearance.
        signature.setAppearanceFlags(Signature.e_APFlagLabel | Signature.e_APFlagDN | Signature.e_APFlagText
                | Signature.e_APFlagLocation | Signature.e_APFlagReason | Signature.e_APFlagSigner);
        // Set signer.
        signature.setKeyValue(Signature.e_KeyNameSigner, "Foxit");
        // Set location.
        signature.setKeyValue(Signature.e_KeyNameLocation, "AnyWhere");
        // Set reason.
        signature.setKeyValue(Signature.e_KeyNameReason, "ANyReason");
        // Set contact info.
        signature.setKeyValue(Signature.e_KeyNameContactInfo, "AnyInfo");
        // Set domain name.
        signature.setKeyValue(Signature.e_KeyNameDN, "AnyDN");
        // Set description.
        signature.setKeyValue(Signature.e_KeyNameText, "AnyContent");
        // Filter "Adobe.PPKLite" is supported by default.
        signature.setFilter("Adobe.PPKLite");
        // SubFilter "adbe.pkcs7.sha1" or "adbe.pkcs7.detached" are supported by default.
        signature.setSubFilter("adbe.pkcs7.detached");

        // The input PKCS#12 format certificate, which contains the public and private keys.
        String certPath = "/somewhere/cert.pfx";
        // Password for that certificate.
        byte[] certPassword = "123".getBytes();
        String signedPDFPath = "/somewhere/signed.pdf";
        // Start to sign the signature, if everything goes well, the signed PDF will be saved to the path specified by "save_path".
        Progressive progressive = signature.startSign(certPath, certPassword, Signature.e_DigestSHA1, signedPDFPath, null, null);
        if (progressive != null) {
            int state = Progressive.e_ToBeContinued;
            while (state == Progressive.e_ToBeContinued) {
                state = progressive.resume();
            }
            if (state != Progressive.e_Finished) return;
        }

        // Get the signatures from the signed PDF document, then verify them all.
        PDFDoc pdfDoc = new PDFDoc(signedPDFPath);
        int err = pdfDoc.load(null);
        if (err != Constants.e_ErrSuccess) return;
        int count = pdfDoc.getSignatureCount();
        for (int i = 0; i < count; i++) {
            Signature sign = pdfDoc.getSignature(i);
            if (sign != null && !sign.isEmpty()) {
                Progressive progressive_1 = sign.startVerify(null, null);
                if (progressive_1 != null) {
                    int state = Progressive.e_ToBeContinued;
                    while (state == Progressive.e_ToBeContinued) {
                        state = progressive_1.resume();
                    }
                    if (state != Progressive.e_Finished) continue;
                }
                int verifiedState = sign.getState();
                if ((verifiedState & sign.e_StateVerifyValid) == sign.e_StateVerifyValid) {
                    Log.d("Signature", "addNewSignatureAndSign: Signature" + i + "is valid.");
                }
            }
        }
    } catch (PDFException e) {
        e.printStackTrace();
    }
}

 

创建自定义工具

使用Foxit PDF SDK for Android创建自定义工具非常简单。 UI Extensions Component中已经实现了一些工具,开发人员可以在这些工具的基础上进行二次开发,或者参考这些工具来创建新的工具。为了快速创建自己的工具,我们建议您参阅 “libs” 文件夹下的uiextensions_src工程。

创建一个新的自定义工具,最主要的步骤是创建一个Java类,然后实现 “ToolHandler.java” 接口。

在本节中,我们通过创建一个区域屏幕截图工具来展示如何使用Foxit PDF SDK for Android创建一个自定义工具。该工具帮助用户在PDF页面中选择一个区域进行截图,并将其保存为图片。现在,让我们开始吧。

为方便起见,我们将基于 “samples” 文件夹下的 “viewer_ctrl_demo” 工程来构建该工具。实现该工具的步骤如下:

  • 创建名为ScreenCaptureToolHandler的Java类,该类实现 “ToolHandler.java” 接口。
  • 处理onTouchEventonDraw事件。
  • 实例化ScreenCaptureToolHandler对象,然后将其注册到UIExtensionsManager。
  • ScreenCaptureToolHandler对象设置为当前的tool handler。

步骤1: 创建一个名为ScreenCaptureToolHandler的Java类,该类实现”ToolHandler.java“接口。

a)在Android Studio中加载 “viewer_ctrl_demo” 工程。在 “com.foxit.pdf.viewctrl” 包中创建名为 “ScreenCaptureToolHandler” 的Java类。

b)ScreenCaptureToolHandler.java类实现ToolHandler接口,如Figure 6-1所示。

Figure 6-1

步骤 2: 处理onTouchEventonDraw事件。

更新ScreenCaptureToolHandler.java,如下所示:

package com.foxit.pdf.pdfviewer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.MotionEvent;
import android.widget.Toast;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.PDFException;
import com.foxit.sdk.common.Progressive;
import com.foxit.sdk.common.fxcrt.Matrix2D;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.common.Renderer;
import com.foxit.uiextensions.ToolHandler;
import com.foxit.uiextensions.UIExtensionsManager;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class ScreenCaptureToolHandler implements ToolHandler {

    private Context mContext;
    private PDFViewCtrl mPdfViewCtrl;

    public ScreenCaptureToolHandler(Context context, PDFViewCtrl pdfViewCtrl) {
        mPdfViewCtrl = pdfViewCtrl;
        mContext = context;
    }

    @Override
    public String getType() {
        return "";
    }

    @Override
    public void onActivate() {

    }

    @Override
    public void onDeactivate() {

    }

    private PointF mStartPoint = new PointF(0, 0);
    private PointF mEndPoint = new PointF(0, 0);
    private PointF mDownPoint = new PointF(0, 0);
    private Rect mRect = new Rect(0, 0, 0, 0);
    private RectF mNowRect = new RectF(0, 0, 0, 0);
    private int mLastPageIndex = -1;

    // Handle OnTouch event
    @Override
    public boolean onTouchEvent(int pageIndex, MotionEvent motionEvent) {
        // Get the display view point in device coordinate system
        PointF devPt = new PointF(motionEvent.getX(), motionEvent.getY());
        PointF point = new PointF();
        // Convert display view point to page view point.
        mPdfViewCtrl.convertDisplayViewPtToPageViewPt(devPt, point, pageIndex);
        float x = point.x;
        float y = point.y;

        switch (motionEvent.getAction()) {
            // Handle ACTION_DOWN event: get the coordinates of the StartPoint.
            case MotionEvent.ACTION_DOWN:
                if (mLastPageIndex == -1 || mLastPageIndex == pageIndex) {
                    mStartPoint.x = x;
                    mStartPoint.y = y;
                    mEndPoint.x = x;
                    mEndPoint.y = y;
                    mDownPoint.set(x, y);
                    if (mLastPageIndex == -1) {
                        mLastPageIndex = pageIndex;
                    }
                }
                return true;

            // Handle ACTION_Move event.
            case MotionEvent.ACTION_MOVE:
                if (mLastPageIndex != pageIndex)
                    break;
                if (!mDownPoint.equals(x, y)) {
                    mEndPoint.x = x;
                    mEndPoint.y = y;

                    // Get the coordinates of the Rect.
                    getDrawRect(mStartPoint.x, mStartPoint.y, mEndPoint.x, mEndPoint.y);

                    // Convert the coordinates of the Rect from float to integer.
                    mRect.set((int) mNowRect.left, (int) mNowRect.top, (int) mNowRect.right, (int) mNowRect.bottom);

                    // Refresh the PdfViewCtrl, then the onDraw event will be triggered.
                    mPdfViewCtrl.refresh(pageIndex, mRect);
                    mDownPoint.set(x, y);
                }
                return true;

            // Save the selected area as a bitmap.
            case MotionEvent.ACTION_UP:
                if (mLastPageIndex != pageIndex)
                    break;
                if (!mStartPoint.equals(mEndPoint.x, mEndPoint.y)) {
                    renderToBmp(pageIndex, "/mnt/sdcard/ScreenCapture.bmp");
                    Toast.makeText(mContext, "The selected area was saved as a bitmap stored in the /mnt/sdcard/ScreenCapture.bmp", Toast.LENGTH_LONG).show();
                }
                mDownPoint.set(0, 0);
                mLastPageIndex = -1;
                return true;
            default:
                return true;
        }
        return true;
    }

    // Save a bimap to a specified path.
    public static void saveBitmap(Bitmap bm, String outPath) throws IOException {
        File file = new File(outPath);
        file.createNewFile();

        FileOutputStream fileout = null;
        try {
            fileout = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        bm.compress(Bitmap.CompressFormat.JPEG, 100, fileout);
        try {
            fileout.flush();
            fileout.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // Render the selected area to a bitmap.
    private void renderToBmp(int pageIndex, String filePath) {
        try {
            PDFPage page = mPdfViewCtrl.getDoc().getPage(pageIndex);

            mPdfViewCtrl.convertPageViewRectToPdfRect(mNowRect, mNowRect, pageIndex);
            int width = (int) page.getWidth();
            int height = (int) page.getHeight();

            Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            bmp.eraseColor(Color.WHITE);

            // Create a Renderer object
            Renderer renderer = new Renderer(bmp, true);

            // Get the display matrix.
            Matrix2D matrix = page.getDisplayMatrix(0, 0, width, height, 0);
            Progressive progress = renderer.startRender(page, matrix, null);
            int state = Progressive.e_ToBeContinued;
            while (state == Progressive.e_ToBeContinued) {
                state = progress.resume();
            }

            // Create a bitmap with the size of the selected area.
            bmp = Bitmap.createBitmap(bmp, (int) mNowRect.left, (int) (height - mNowRect.top), (int) mNowRect.width(), (int) Math.abs(mNowRect.height()));
            try {
                saveBitmap(bmp, filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (PDFException e) {
            e.printStackTrace();
        }
    }

    // Get the coordinates of a Rect.
    private void getDrawRect(float x1, float y1, float x2, float y2) {
        float minx = Math.min(x1, x2);
        float miny = Math.min(y1, y2);
        float maxx = Math.max(x1, x2);
        float maxy = Math.max(y1, y2);

        mNowRect.left = minx;
        mNowRect.top = miny;
        mNowRect.right = maxx;
        mNowRect.bottom = maxy;
    }

    @Override
    public boolean onLongPress(int pageIndex, MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onSingleTapConfirmed(int pageIndex, MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean isContinueAddAnnot() {
        return false;
    }

    @Override
    public void setContinueAddAnnot(boolean continueAddAnnot) {
    }
    
    // Handle the drawing event.
    @Override
    public void onDraw(int i, Canvas canvas) {
        if (((UIExtensionsManager) mPdfViewCtrl.getUIExtensionsManager()).getCurrentToolHandler() != this)
            return;
        if (mLastPageIndex != i) {
            return;
        }
        canvas.save();
        Paint mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.BLUE);
        mPaint.setAlpha(200);
        mPaint.setStrokeWidth(3);
        canvas.drawRect(mNowRect, mPaint);
        canvas.restore();
    }
}

步骤 3: 实例化ScreenCaptureToolHandler对象,然后将其注册到UIExtensionsManager。

private ScreenCaptureToolHandler screenCapture = null;
...
screenCapture = new ScreenCaptureToolHandler(mContext, pdfViewCtrl);
uiExtensionsManager.registerToolHandler(screenCapture);

步骤4ScreenCaptureToolHandler对象设置为当前的tool handler。

uiExtensionsManager.setCurrentToolHandler(screenCapture);

现在,我们已经完成了自定义工具的创建。为了验证该工具,我们需要在MainActivity.java中添加一个action 菜单,以及步骤3和步骤4中的代码。

首先,在 “app/src/main/res/menu” 目录下的Main.xml文件中添加一个action菜单,如下所示。

<item
   android:id="@+id/ScreenCapture"
   android:title="@string/screencapture"/>

在 “app/src/main/res/values/strings.xml” 中,添加以下字符串:

<string name="screencapture">ScreenCapture</string>

然后,将以下代码添加到MainActivity.java中的onActionItemClicked()函数中。

if (itemId == R.id.ScreenCapture) {
    if (screenCapture == null) {
        screenCapture = new ScreenCaptureToolHandler(mContext, pdfViewCtrl);
        uiExtensionsManager.registerToolHandler(screenCapture);
    }
    uiExtensionsManager.setCurrentToolHandler(screenCapture);
}

请首先实例化一个ScreenCaptureToolHandler对象,如 (private ScreenCaptureToolHandler screenCapture = null;)。

完成上述所有工作后,可以编译和运行demo了。

备注: 在这里,我们使用AVD 9.0运行该demo。请确保您已经将 Sample.pdf 文档添加模拟器SD中的正常位置 (demo中的文件路径匹配)

当编译完demo并在模拟器上安装APK后,在弹出的窗口点击 “Allow” 允许demo访问设备上的文件。点击页面上的任意位置,会出现上下文操作栏,然后点击(更多按钮) ,找到ScreenCapture菜单项,如Figure 6-2所示。

Figure 6-2

单击ScreenCapture,在PDF页面长按并选择一个矩形区域,然后将弹出一个如Figure 6-3所示的消息框。该消息框指示bitmap (选定区域) 保存的位置。

Figure 6-3

为了验证该工具是否成功截取和保存了所选区域,我们需要找到保存的屏幕截图。单击IDE右下角的Device File Explorer,可以在SD卡中看到名为 “ScreenCapture.bmp” 的屏幕截图,如Figure 6-4所示。

Figure 6-4

右键单击 “ScreenCapture.bmp” 图片,选择 “Save AS…” 将其保存到所需的位置。打开图片,可以看到其如Figure 6-5所示。

Figure 6-5

如您所见,我们已成功创建了区域屏幕截图工具。这只是一个示例,用来说明如何使用Foxit PDF SDK for Android创建自定义工具。您可以参考该示例或者我们的demos来开发您需要的工具。

 

使用Cordova实现Foxit PDF SDK for Android

在开发跨平台移动应用程序时,Apache Cordova是一个理想的开源框架。 ‘cordova-plugin-foxitpdf‘是我们提供的使用Foxit PDF SDK for Android的移动框架插件之一。该插件帮助您可以使用Cordova框架实现强大的PDF viewing功能。通过此插件,您可以预览任何PDF文件,包括PDF 2.0标准的文件,XFA文档和受RMS加密的文档,您还可以注释和编辑PDF文档。

本节将帮助您在Windows平台开始使用Foxit PDF SDK for Android 7.4和Cordova 7.4插件。对于其他操作系统,您可以参考本教程。对于7.4版本之前的插件的用法,请参阅网站https://github.com/foxitsoftware/cordova-plugin-foxitpdf

系统要求

  • NPM
  • Cordova 9.0.0
  • Android SDK
  • JDK 1.8
  • Foxit PDF SDK For Android 7.4

备注: Foxit PDF SDK for Android版本应与 ‘cordova-plugin-foxitpdf’插件的版本相匹配。在安装时可以指定插件版本。如果不指定,则安装最新版本。

安装Cordova 命令行工具

请参阅Apache Cordova website来安装Cordova命令行工具。

下载 和安装Node.js

打开命令行终端,输入npm install -g cordova.

使用Foxit PDF SDK for Android构建一个Cordova工程

新建一个Cordova工程

打开命令行或终端,导航到您要创建工程的目录,输入cordova create <path> 命令。例如,导航到 “D:\cordova”,输入如下的命令创建一个cordova工程:

cordova create test_cordova com.app Test_Cordova

添加平台

创建Cordova工程后,导航到该工程目录下(D:\cordova\test_cordova),然后输入如下的命令添加Android平台来编译您的应用程序。

cd test_cordova
cordova platform add android

安装 ‘cordova-plugin-foxitpdf’ 插件

选择下面的其中一种方式来安装 ‘cordova-plugin-foxitpdf’:

  • 从npm下载插件,然后在工程目录下安装:

// 安装特定的插件版本,比如7.4版本

cordova plugin add cordova-plugin-foxitpdf@7.4

// 安装最新的插件版本 (不指定版本号)

cordova plugin add cordova-plugin-foxitpdf
  • 通过repo url直接安装 (通常,这种方式会安装最新的插件版本):
cordova plugin add https://github.com/foxitsoftware/cordova-plugin-foxitpdf.git

备注本教程只介绍7.4版本插件的用法因此确保安装7.4版本的插件。

集成Foxit PDF SDK for Android

备注:在本节中,我们只介绍如何使用Cordova插件来打开一个PDF文档。关于插件的更多API介绍,请参考网站https://github.com/foxitsoftware/cordova-plugin-foxitpdf

下载Foxit PDF SDK for Android (7.4版本)包并解压,然后按照如下的步骤操作:

1)迁移到AndroidX“D:\cordova\test_cordova\platforms\android\gradle.properties” 文件中添加如下的配置:

android.useAndroidX=true
android.enableJetifier=true

2)将解压包中的 “libs” 文件夹拷贝到 “D:\cordova\test_cordova\platforms\android”目录。

3)初始化SDK库。license文件 “rdk_sn.txt” 和 “rdk_key.txt” 在Foxit PDF SDK for Android包的 “libs” 文件夹下。在 “test_cordova\www\js\index.js” 文件中添加如Figure 7-1所示的初始化代码。

Figure 7-1

var foxit_sn = 'xxx'; // The value can be found in the "rdk_sn.txt" (the string after "SN=").
var foxit_key = 'xxx'; // The value can be found in the "rdk_key.txt" (the string after "Sign=").
        
window.FoxitPdf.initialize(foxit_sn, foxit_key);

4)在 “test_cordova\www\js\index.js” 文件中添加如Figure 7-2所示的代码去打开一个PDF文档。

Figure 7-2

var path = '/mnt/sdcard/FoxitSDK/complete_pdf_viewer_guide_android.pdf'; // The PDF file path.
var password = ' '; // The password of the PDF file.
window.FoxitPdf.openDocument(path, password);

var savePath = '/mnt/sdcard/FoxitSDK/complete_pdf_viewer_guide_android_save.pdf'; // The PDF file path for the new saved file.
window.FoxitPdf.setSavePath(savePath);

备注:请确保您已经将Foxit PDF SDK for Android包中 samples/test_files 文件夹下的 complete_pdf_viewer_guide_android.pdf 文档添加到用于运行该工程的Android设备或者模拟器的 FoxitSDK 文件夹中。当然,您可以修改文件路径,使用自己的文件。

5)将 “test_cordova”目录下的”www”文件内容部署到平台 (test_cordova\platforms\android\app\src\main\assets\www)下的 “www”文件中。在终端,导航到工程目录,输入如下的命令:

cordova prepare android

运行工程

运行工程前,您需要首先启动Android设备或者模拟器,然后使用下面的命令或者直接在Android Studio中运行。

导航到项目目录,输入如下的命令:

cordova run android                   // for device
cordova run android --emulator      // for emulator

备注:如果您遇到 AAPT: error: resource android: attr/fontVariationSettings not found 的问题,请将下面的代码拷贝到 test_cordova\platforms\android\app\build.gradle 文件的末尾,或者您可以将Android SDK升级到28或者更高版本。

configurations.all {
    resolutionStrategy {
        force 'androidx.legacy:legacy-support-v4:1.0.0'
    }
}

当成功运行该工程后,”complete_pdf_viewer_guide_android.pdf” 文档将显示如Figure 7-3所示。

Figure 7-3

自定义UI

通过一个配置文件来自定义UI

您可以通过一个名为”uiextensions_config.json”的JSON文件来自定义UI,包括功能模块,权限管理和UI元素的属性,该文件位于”test_cordova\platforms\android\app\src\main\assets\www\plugins\cordova-plugin-foxitpdf” 文件夹下。

自定义UI,只需要根据需求修改JSON文件,然后重新运行工程。有关JSON文件的更详细信息,请参阅4.1.2小节 配置项描述 .

备注:如果在 test_cordova\platforms\android\app\src\main\assets\www\plugins\cordova-plugin-foxitpdf 文件夹下没有找到 uiextensions_config.json 文件,您可以手动将test_cordova\node_modules\cordova-plugin-foxitpdf\src\android\assets 目录下的JSON文件拷贝过去。

通过源代码自定义UI

UI实现封装在 “libs” 文件夹下的FoxitRDKUIExtensions.aar中。如果您不想使用现有的UI框架,您可以通过修改源代码来进行重新设计。在 “foxitpdfsdk_7_4_android\libs” 文件夹下找到源代码工程 “uiextensions_src“,在Android Studio中打开该工程,根据您的需要进行修改。然后,重新编译该工程,并重新打包FoxitRDKUIExtensions.aar文件,并将您cordova工程中的该文件进行替换。

如果您需要自定义扫描功能UI,您可以在 “foxitpdfsdk_7_4_android\libs” 文件夹下找到源代码工程 “pdfscan“,在Android Studio中打开该工程,根据您的需要进行修改。然后,重新编译该工程,并重新打包FoxitPDFScan-UI.aar文件,并将您cordova工程中的该文件进行替换。

 

使用React Native实现Foxit PDF SDK for Android

React Native是一个使用JavaScript和React构建native应用程序的开源移动开发框架。’react-native-foxitpdf‘是我们提供的使用Foxit PDF SDK for Android的移动框架插件之一。该插件帮助您可以使用React Native框架实现强大的PDF viewing功能。通过此插件,您可以预览任何PDF文件,包括PDF 2.0标准的文件,XFA文档和受RMS加密的文档,您还可以注释和编辑PDF文档。

本节将帮助您在Windows平台开始使用Foxit PDF SDK for Android 7.4和React Native 7.4插件。对于其他操作系统,您可以参考本教程。对于7.4版本之前的插件的用法,请参阅网站https://github.com/foxitsoftware/react-native-foxitpdf

系统要求

  • NPM 8.3 or newer
  • React-Native
  • Android SDK
  • Android Gradle plugin 3.1.0 or higher
  • Gradle 4.4 or higher
  • JDK 1.8
  • Foxit PDF SDK for Android 7.4

备注:

  • Foxit PDF SDK for Android版本应与 ‘react-native-foxitpdf’插件的版本相匹配。在安装时,您可以指定插件版本。如果不指定,则将安装最新版本。
  • 对于 ‘react-native-foxitpdf’插件7.0版本之前的插件需要0.60.0以下版本的React-Native,而7.0之后版本的插件需要0.60.0以上版本React-Native

安装React Native命令行工具

在Android平台构建React Native应用程序,您需要Node, React Native命令行工具, JDK 以及 Android Studio.

请参阅官方的入门指南设置React Native环境设置Android 开发环境 来安装React Native 命令行工具和配置Android开发环境。

使用Foxit PDF SDK for Android构建一个React Native工程

新建一个React Native工程

打开命令行或终端,导航到您要创建工程的目录,输入react-native init <ProjectName> 命令。例如,导航到 “D:\react-native”,输入如下的命令创建一个react-native工程:

react-native init testRN

安装’react-native-foxitpdf’ 插件

从npm下载插件,然后在工程目录下安装。您可以安装特定版本或最新版本的插件。

  • // 安装特定的插件版本,比如7.4版本
cd testRN
npm install @foxitsoftware/react-native-foxitpdf@7.4 --save  
  • // 安装最新的插件版本 (不指定版本号)
cd testRN
npm install @foxitsoftware/react-native-foxitpdf --save  

备注:

  • 后面所有的命令也都工程项目目录下执行。
  • 本教程只介绍7.4版本插件的用法,因此请确保安装7.4版本的插件。

集成Foxit PDF SDK for Android

备注:在本节中,我们只介绍如何使用React Native插件来打开一个PDF文档。关于插件的更多API介绍,请参考网站https://github.com/foxitsoftware/react-native-foxitpdf

下载Foxit PDF SDK for Android (7.4版本)包并解压,然后按照如下的步骤操作:

1)将解压包中的 “libs” 文件夹拷贝到 “D:\react-native\testRN\android”目录。

2)将下面的代码加入到工程级的build.gradle文件中 (testRN\android\build.gradle):

在 ‘allprojects‘,添加如下代码:

allprojects {
    repositories {
        mavenLocal()
        google()
        jcenter()
        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/../node_modules/react-native/android"
        }
        flatDir {
             dirs project(':@foxitsoftware_react-native-foxitpdf').file("$rootDir/libs")
        }
    }
}

3)启用Multi-Dex。

在模块级的build.gradle (testRN\android\app\build.gradle)中,添加如下的代码:

...
android {
    ...
    defaultConfig {
        applicationId "com.testRN"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
    }
    ...
}
...

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "com.facebook.react:react-native:+"  // From node_modules
    implementation 'androidx.multidex:multidex:2.0.1'
    ...
}
...

4)在 “testRN\android\app\src\main\AndroidManifest.xml” 文件中,添加所需的权限和申明PDFReaderActivity。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.testrn">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <application
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:allowBackup="false"
        android:theme="@style/AppTheme"
        tools:replace="android:allowBackup,icon,theme,label,name">

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
            android:windowSoftInputMode="adjustResize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />

        <activity
            android:name="com.foxitreader.PDFReaderActivity"
            android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
            android:screenOrientation="fullSensor" />

    </application>

</manifest>

5)使用插件打开一个PDF文档。

在 “testRN\App.js” 文件中,您可以使用如下的代码导入插件:

import FoxitPDF from '@foxitsoftware/react-native-foxitpdf';

调用如下的接口初始化SDK库:

FoxitPDF.initialize("foxit_sn", "foxit_key");

备注 foxit_sn的值在rdk_sn.txt (SN=后面的字符串)foxit_key的值在 rdk_key.txt (Sign=后面的字符串)License文件 rdk_sn.txt rdk_key.txt Foxit PDF SDK for Android包的 libs 文件夹下。

然后,调用如下的接口打开一个PDF文档:

FoxitPDF.openDocument('a PDF file path');

更新 “App.js” 文件,添加一个button用来打开PDF文档:

import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import FoxitPDF from '@foxitsoftware/react-native-foxitpdf';

const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
  android:
    'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

type Props = {};
export default class App extends Component<Props> {

  constructor(props) {
    super(props);
    // The "foxit_sn" can be found in the "rdk_sn.txt" file (the string after "SN=").
    // The "foxit_key" can be found in the "rdk_key.txt" file (the string after "Sign=").
    FoxitPDF.initialize("foxit_sn", "foxit_key"); 

  onPress() {
    FoxitPDF.openDocument('mnt/sdcard/FoxitSDK/complete_pdf_viewer_guide_android.pdf');
  }

  render() {
    return (
      <View style={styles.container}>
        <TouchableOpacity
          style={styles.button}
          onPress={this.onPress}
        >
          <Text> Open PDF </Text>
        </TouchableOpacity>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  button: {
    alignItems: 'center',
    backgroundColor: '#DDDDDD',
    padding: 10
  }, 
});

备注:

  • FoxitPDF.openDocument的用法等同于FoxitPDF.openPDF但是我们推荐使用FoxitPDF.openDocument因为FoxitPDF.openPDF在以后的版本中可能被弃用在调用FoxitPDF.openDocument之前,您必须首先调用FoxitPDF.initialize初始化SDK库。
  • 请确保您已经将Foxit PDF SDK for Android包中 samples/test_files 文件夹下的 complete_pdf_viewer_guide_android.pdf 文档添加到用于运行该工程的Android设备或者模拟器 FoxitSDK 文件夹中。当然,您可以修改文件路径,使用自己的文件。

运行工程

运行工程前,您需要首先启动Android设备或者模拟器,在命令行中导航到工程目录,然后输入如下的命令:

react-native run-android 

当成功运行该工程后,点击 “Open PDF” 按钮。”complete_pdf_viewer_guide_android.pdf” 文档将显示如Figure 8-1所示。

Figure 8-1

备注:如果您在运行遇到 Unable to load script from assets ‘index.android.bundle’. Make sure your bundle is packaged correctly or you’re running a packager server. 问题,可以执行以下步骤:

1)在 “testRN\android\app\src\main”文件夹下创建一个名为 ‘assets’的文件夹,或者在命令行或终端,导航工程目录,使用如下命令创建一个文件夹:

cd android/app/src/main
mkdir assets

2)导航到工程目录,输入如下的命令:

react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res

3)输入 react-native run-android 命令重新运行工程。

自定义UI

通过一个配置文件来自定义UI

您可以通过一个名为”uiextensions_config.json”的JSON文件来自定义UI,包括功能模块,权限管理和UI元素的属性,该文件位于”testRN\node_modules\@foxitsoftware\react-native-foxitpdf\lib\android\src\main\res\raw” 文件夹下。

自定义UI,只需要根据需求修改JSON文件,然后重新运行工程。有关JSON文件的更详细信息,请参阅4.1.2小节 配置项描述。

通过源代码自定义UI

UI实现封装在 “libs” 文件夹下的FoxitRDKUIExtensions.aar中。如果您不想使用现有的UI框架,您可以通过修改源代码来进行重新设计。在 “foxitpdfsdk_7_4_android\libs” 文件夹下找到源代码工程 “uiextensions_src“,在Android Studio中打开该工程,根据您的需要进行修改。然后,重新编译该工程,并重新打包FoxitRDKUIExtensions.aar文件,并将您cordova工程中的该文件进行替换。

 

使用Xamarin实现Foxit PDF SDK for Android

Xamarin是一个使用共享C#代码库构建native应用程序的跨平台开发框架。我们为Android和iOS平台分别提供了对应的bindings for Android and iOS (‘foxit_xamarin_android’和’foxit_xamarin_ios’),以便开发人员可以将Foxit PDF SDK强大的PDF功能无缝地集成到他们的Xamarin应用程序中。

本节将帮助您在Windows平台开始使用Foxit PDF SDK for Android 7.4和Xamarin 7.4插件。对于其他操作系统,您可以参考本教程。对于7.4版本之前的插件的用法,请参阅网站https://github.com/foxitsoftware/xamarin-foxitpdf

系统要求

  • Visual Studio 2019
  • TargetFrameworkVersion >= Android 9.0
  • Minimum Android version: Android 4.4 (API 19)
  • Recommended target SDK version: API 29
  • Android SDK
  • JDK >= 1.8
  • Foxit PDF SDK for Android 7.4

备注: Foxit PDF SDK for Android版本应与 foxit_xamarin_android插件的版本相匹配。

在Visual Studio 2019中安装Xamarin

请参阅官方安装说明手册 在Visual Studio 2019中安装Xamarin。

集成Foxit PDF SDK到您的Xamarin工程中

集成Foxit PDF SDK到您的Xamarin工程中,有如下的两种方式:

  • 通过 NuGet集成 (从6.3版本开始支持)
  • 通过编译和引用DLLs的方式手动集成

通过NuGet集成

现在可以通过NuGet包将Foxit PDF SDK集成到您的xamarin项目中,这种方式比第二种 “通过编译和引用DLLs的方式手动集成” 更简单,并且可以为您节省大量时间。

通过NuGet包将Foxit PDF SDK集成到您的xamarin项目中,请按照如下的步骤:

1)在Solution Explorer中,右击您工程的References节点,点击Manage NuGet Packages…,如Figure 9-1所示。

Figure 9-1

2)然后,选择Browse选项卡,搜索Foxit.Android, Foxit.Android.UIExtensions ,并安装以上两个包,如Figure 9-2所示。

Figure 9-2

备注:7.3版本开始,当安装Foxit.Android.UIExtensions以下的包都会被自动安装:

Foxit.Android.Microsoft.Aad.Aadl
Foxit.Android.Microsoft.Identity.Common
Foxit.Android.RMSSDK
Foxit.Android.RMSSDK.UI
Foxit.Android.Cropper
Foxit.Android.RxAndroid
Foxit.Android.Xcrash

安装完成后,自动安装包将不会工程References下显示。

(可选) 如果您需要使用扫描功能,那么您还需要安装Foxit.Android.ScanningFoxit.Android.Scanning.UI包,如Figure 9-2所示的。

这里,我们将安装Foxit.Android, Foxit.Android.UIExtensions, Foxit.Android.ScanningFoxit.Android.Scanning.UI包。完成安装后,工程的References将如Figure 9-3所示。

Figure 9-3

在 “foxit_xamarin_android” 文件夹下的complete_pdf_viewer是一个示例工程,该工程构建了一个功能丰富的PDF阅读器。该工程已通过Nuget包的方式集成了Foxit PDF SDK,因此您可以直接编译运行。

通过编译和引用DLLs的方式手动集成

手动集成Foxit PDF SDK到您的应用程序中,您需要首先编译DLLs,然后在您的工程中引用该DLLs.

编译DLLs

从GitHub上下载 ‘foxit_xamarin_android‘,然后编译相关工程以获取DLLs,请按照如下的步骤:

1)下载Foxit PDF SDK for Android 包并解压,将包中 “libs” 文件夹下的如下文件(库和licenses) 拷贝到 “foxit_xamarin_android\libs” 目录下:

FoxitRDK.aar

FoxitRDKUIExtensions.aar

FoxitMobileScanningRDK.aar

FoxitPDFScan-UI.aar

rdk_key.txt

rdk_sn.txt

2)编译获取DLLs。您需要编辑相应的工程来获取如下的DLLs:

FoxitRDK.dll

Microsoft_Aad_Adal.dll

MS_Common.dll

RMSSDK.dll

RMSSDK_UI.dll

XCrash.dll

FoxitUIExtensions.dll

RxAndroid.dll

Cropper.dll

获取以上的DLLs,您需要按照如下的步骤:

1) 在Visual Studio 2019中打开”foxit_xamarin_android\FoxitUIExtensions” 目录下的sln

2) 右键点击FoxitUIExtensions工程,选择Build-> Build Solution来编译工程。

3) 如果编译成功,则

  • “foxit_xamarin_android\FoxitRDK\FoxitRDK\bin\Debug (or release)” 目录下将生成dll, Microsoft_Aad_Adal.dll, MS_Common.dll, RMSSDK.dll, RMSSDK_UI.dll, XCrash.dll
  • “foxit_xamarin_android\FoxitUIExtensions\FoxitUIExtensions\bin\Debug (or release)” 目录下将生成FoxitUIExtensions.dll, RxAndroid.dll 和 Cropper.dll

备注:

  • 如果您需要使用扫描功能需要编译 “foxit_xamarin_android” 文件夹下FoxitMobileScanningRDK FoxitPDFScan_UI工程来获取DLLs然后在您的工程中引用这些DLLs
在工程中引用DLLs

本节以FoxitRDK.dll 为例来展示如何在您的工程中引用该DLL。对于其他的DLLs,执行与FoxitRDK.dll相同的步骤。我们假设你已经获取了FoxitRDK.dll,如果没有,请参阅 编译DLLs 小节来编译FoxitRDK 工程,生成FoxitRDK.dll

1)在Solution Explorer 中,右击工程的References 节点,选择Add Reference…,如Figure 9-4所示。

Figure 9-4

2)在Reference Manager 窗口,点击Browse… (见 Figure 9-5) 定位到FoxitRDK.dll (在”foxit_xamarin_android\FoxitRDK\FoxitRDK\bin\Debug (or release)” 文件夹下),选择并点击Add,如Figure 9-6所示。

Figure 9-5

Figure 9-6

然后,Reference Manager 窗口的列表中将出现FoxitRDK.dll,并且已经被勾选 (见 Figure 9-7)。点击OK

Figure 9-7

3)在完成 以上步骤后,工程的References将如Figure 9-8所示。

Figure 9-8

参考引用FoxitRDK.dll的步骤在工程中引用如下的DLLs:

Microsoft_Aad_Adal.dll

MS_Common.dll

RMSSDK.dll

RMSSDK_UI.dll

XCrash.dll

FoxitUIExtensions.dll

RxAndroid.dll

Cropper.dll

备注:(可选) 如果您需要使用扫描功能参阅 编译DLLs 小节来编译 “foxit_xamarin_android” 文件夹下FoxitMobileScanningRDK FoxitPDFScan_UI工程,以获取如下DLLs:

FoxitMobileScanningRDK\FoxitMobileScanningRDK\bin\Debug (or release)\FoxitMobileScanningRDK.dll
FoxitPDFScan_UI\FoxitPDFScan_UI\bin\Debug (or release)\FoxitPDFScan_UI.dll

在工程中添加以上两个DLLs库的引用 (参阅添加FoxitRDK.dll步骤)

在完成以上所有步骤后,工程的References将如Figure 9-9所示。

Figure 9-9

4)安装Xamarin.AndroidX 和其相关的Nuget 包。在 Solution Explorer右击工程的References 节点,选择 Manage NuGet Packages… Figure 9-1所示。

然后选择Browse选项卡,搜索如下的Nuget包:

Xamarin.AndroidX.Browser
Xamarin.Google.Android.Material    
Xamarin.AndroidX.AppCompat
Xamarin.AndroidX.Legacy.Support.V4
Xamarin.AndroidX.Lifecycle.LiveData
Xamarin.Android.Support.v7.AppCompat (required for opening a RMS protected PDF file)

当安装这些Nuget包时,您可能会遇到一些警告,提示您需要首先安装一些其他的包,请根据提示进行安装。

安装完成后工程的References将如Figure 9-10所示。

Figure 9-10

使用Foxit PDF SDK for Android构建一个Xamarin Android工程

本节将帮助您在Xamarin Android平台上快速构建一个功能齐全的PDF阅读器,并提供详细的步骤说明。

创建一个新的Xamarin Android工程

打开Visual Studio 2019,选择File -> New -> Project…开启一个Create a new project 向导。选择C#语言和Android平台,然后选择Android App (Xamarin),如Figure 9-11所示。点击OK

Figure 9-11

Configure your new project 窗口,根据需要设置工程名称 (比如 “TestXamarin“) 以及存放位置,如Figure 9-12所示。然后,点击Create

Figure 9-12

New Android App 窗口中,选择Single View App,如Figure 9-13所示。然后,点击OK

Figure 9-13

集成Foxit PDF SDK到您的工程中

请参阅9.3小节 “集成Foxit PDF SDK到您的Xamarin工程中” 将Foxit PDF SDK集成到创建的工程中。我们推荐使用第一种方式 “通过NuGet集成“,该集成方式更简单和更方便。

初始化 Foxit PDF SDK

在调用任何APIs之前,您需要首先使用Library.Initialize(sn, key)函数来初始化Foxit PDF SDK。下面是SDK库初始化的示例代码。在下一节中将介绍该代码在 “MainActivity.cs” 文件中的位置。

using Com.Foxit.Sdk;


using Com.Foxit.Sdk.Common;


...


String sn = "xxx";


String key = "xxx";
int errCode = Library.Initialize(sn, key);
if (errCode != Constants.EErrSuccess)


  return;

备注: 参数 sn的值在 rdk_sn.txt (SN=后面的字符串)key的值在 rdk_key.txt (Sign=后面的字符串)试用license文件 (rdk_sn.txt rdk_key.txt) “foxit_xamarin_android\libs” 文件夹或者在Foxit PDF SDK for Android “libs”文件夹下。

使用PDFViewCtrl显示PDF文档

显示一个PDF文档,请按照如下的步骤:

1)实例化一个PDFViewCtrl对象来显示一个PDF文档。

在 “MainActivity.cs” 中,实例化一个PDFViewCtrl对象,调用PDFViewCtrl.openDoc函数打开和渲染PDF文档。

using Com.Foxit.Sdk;


...
private String path = "/mnt/sdcard/complete_pdf_viewer_guide_android.pdf";
private PDFViewCtrl pdfViewCtrl;


...
// Instantiate a PDFViewCtrl object.
pdfViewCtrl = new PDFViewCtrl(this.ApplicationContext);

// Open and Render a PDF document.
pdfViewCtrl.OpenDoc(path, null);
SetContentView(pdfViewCtrl);

备注:请确保您已经将Foxit PDF SDK for Android包中 samples/test_files 文件夹下的 complete_pdf_viewer_guide_android.pdf 文档添加到用于运行该工程的Android设备或者模拟器的 SD。当然,您可以修改文件路径,使用自己的文件。

更新MainActivity.cs

using System;
using Android.App;
using Android.OS;
using AndroidX.AppCompat.App;

using Com.Foxit.Sdk;
using Com.Foxit.Sdk.Common;

namespace TestXamarin
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {

        // The value of "sn" can be found in the "rdk_sn.txt".
        // The value of "key" can be found in the "rdk_key.txt".
        private String sn = "xxx";
        private String key = "xxx";

        private String path = "/mnt/sdcard/complete_pdf_viewer_guide_android.pdf";

        private PDFViewCtrl pdfViewCtrl;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Initialize Foxit SDK Library.
            int errCode = Library.Initialize(sn, key);
            if (errCode != Constants.EErrSuccess)
                return;

            // Instantiate a PDFViewCtrl object.
            pdfViewCtrl = new PDFViewCtrl(this.ApplicationContext);

            // Open and Render a PDF document.
            pdfViewCtrl.OpenDoc(path, null);
            SetContentView(pdfViewCtrl);
        }
    }
}

2)设置Android设备或者模拟器的SD卡的读写权限。您需要添加额外的代码申请授权运行时权限。

更新整个MainActivity.cs,如下所示:

using System;
using Android.App;
using Android.OS;
using Android.Runtime;

using Android;
using Android.Content.PM;
using AndroidX.AppCompat.App;
using AndroidX.Core.Content;
using AndroidX.Core.App;

using Com.Foxit.Sdk;
using Com.Foxit.Sdk.Common;

namespace TestXamarin
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        public static int REQUEST_EXTERNAL_STORAGE = 1;
        private static string[] PERMISSIONS_STORAGE = {
            Manifest.Permission.ReadExternalStorage,
            Manifest.Permission.WriteExternalStorage
        };

        // The value of "sn" can be found in the "rdk_sn.txt".
        // The value of "key" can be found in the "rdk_key.txt".
        private String sn = "xxx";
        private String key = "xxx";

        private String path = "/mnt/sdcard/complete_pdf_viewer_guide_android.pdf";

        private PDFViewCtrl pdfViewCtrl;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Initialize Foxit SDK Library.
            int errCode = Library.Initialize(sn, key);
            if (errCode != Constants.EErrSuccess)
                return;

            // Instantiate a PDFViewCtrl object.
            pdfViewCtrl = new PDFViewCtrl(this.ApplicationContext);

            // Require the authorization of runtime permissions.
            if (Build.VERSION.SdkInt > BuildVersionCodes.M)
            {
                Permission permission = ContextCompat.CheckSelfPermission(this.ApplicationContext, Manifest.Permission.WriteExternalStorage);
                if (permission != Permission.Granted)
                {
                    ActivityCompat.RequestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                    return;
                }
            }

            // Open and Render a PDF document.
            pdfViewCtrl.OpenDoc(path, null);
            SetContentView(pdfViewCtrl);
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            if (requestCode == REQUEST_EXTERNAL_STORAGE
             && grantResults[0] == Permission.Granted)
            {
                if (pdfViewCtrl != null)
                {
                    pdfViewCtrl.OpenDoc(path, null);
                }
            }
            else
            {
                base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }
    }
}

在本章中,使用AVD 9.0 (API 28) 来编译和运行该工程,并且使用第二种方法 (申请授权运行时权限) 来获取对模拟器的SD卡的读写权限。

当编译完项目并在模拟器上安装APK后,在弹出的窗口点击”Allow” 允许工程访问设备上的文件。然后您将看到 “complete_pdf_viewer_guide_android.pdf” 文档显示如Figure 9-14所示。该示例应用程序具有一些基本的PDF功能,比如放大/缩小和翻页。

Figure 9-14

打开一个RMS加密的PDF文档

打开一个RMS加密的文档,请按照如下的步骤:

1)在打开RMS加密的文档时需要有UI相关的操作,因此在打开文档之前需要设置关联的activity。

pdfViewCtrl.AttachedActivity = this;

2)处理来自UI操作的activity结果。在onActivityResult函数中调用API “pdfViewCtrl.HandleActivityResult()“。

protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
    pdfViewCtrl.HandleActivityResult(requestCode, (int)resultCode, data);
}

基于上一节 “使用PDFViewCtrl显示PDF文档“,更新整个MainActivity.cs,如下所示:

using System;
using Android.App;
using Android.OS;
using Android.Runtime;

using Android;
using Android.Content.PM;
using AndroidX.AppCompat.App;
using AndroidX.Core.Content;
using AndroidX.Core.App;

using Com.Foxit.Sdk;
using Com.Foxit.Sdk.Common;
using Android.Content;

namespace TestXamarin
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        public static int REQUEST_EXTERNAL_STORAGE = 1;
        private static string[] PERMISSIONS_STORAGE = {
            Manifest.Permission.ReadExternalStorage,
            Manifest.Permission.WriteExternalStorage
        };

        // The value of "sn" can be found in the "rdk_sn.txt".
        // The value of "key" can be found in the "rdk_key.txt".
        private String sn = "xxx";
        private String key = "xxx";

        private String path = "/mnt/sdcard/Sample_RMS.pdf";

        private PDFViewCtrl pdfViewCtrl;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Initialize Foxit SDK Library.
            int errCode = Library.Initialize(sn, key);
            if (errCode != Constants.EErrSuccess)
                return;

            // Instantiate a PDFViewCtrl object.
            pdfViewCtrl = new PDFViewCtrl(this.ApplicationContext);

            // Set the associated activity for RMS UI operations.
            pdfViewCtrl.AttachedActivity = this;

            // Require the authorization of runtime permissions.
            if (Build.VERSION.SdkInt > BuildVersionCodes.M)
            {
                Permission permission = ContextCompat.CheckSelfPermission(this.ApplicationContext, Manifest.Permission.WriteExternalStorage);
                if (permission != Permission.Granted)
                {
                    ActivityCompat.RequestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                    return;
                }
            }

            // Open and Render a PDF document.
            pdfViewCtrl.OpenDoc(path, null);
            SetContentView(pdfViewCtrl);
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            if (requestCode == REQUEST_EXTERNAL_STORAGE
             && grantResults[0] == Permission.Granted)
            {
                if (pdfViewCtrl != null)
                {
                    pdfViewCtrl.OpenDoc(path, null);
                }
            }
            else
            {
                base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }

        // Used for opening a RMS document.
        protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
        {
            pdfViewCtrl.HandleActivityResult(requestCode, (int)resultCode, data);
        }
    }
}

备注:请确保您已经将一个RMS加密的文档比如 Sample_RMS.pdf添加到用于运行该工程的Android设备或者模拟器的SD卡中

运行工程

当编译完项目并在模拟器上安装APK后,在弹出的窗口点击”Allow” 允许工程访问设备上的文件。然后您将看到如Figure 9-15所示的界面,提示您输入组织电子邮件,密码,然后您就可以打开该RMS加密的文档。

Figure 9-15

构建一个功能齐全的PDF阅读器

构建一个功能齐全的PDF阅读器 (比如包含注释、编辑等功能),请确保工程已经添加了对RxAndroid, CropperUIExtensions 库的引用。如果没有,请参阅9.3小节 “集成Foxit PDF SDK到您的Xamarin工程中“。然后,按照如下的步骤:

1)在 “TestXamarin\Properties” 文件夹下的 “AndroidManifest.xml” 文件中设置需要的权限 (见Figure 9-16)。

Figure 9-16

<uses-permission android:name="android.permission.RUN_INSTRUMENTATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

2)在 “MainActivity.cs” 中添加如下的代码。

a)将系统主题设置为 “No Title” 模式,并且将窗口设置为全屏,否则内置功能的布局可能会被影响。

// Turn off the title at the top of the screen.
RequestWindowFeature(WindowFeatures.NoTitle);

// Set the window to Fullscreen.
Window.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.Fullscreen);

b)实例化一个UIExtensionsManager对象,并且设置给PDFViewCtrl

using Com.Foxit.Uiextensions;


...
private UIExtensionsManager uiExtensionsManager;


...
uiExtensionsManager = new UIExtensionsManager(this.ApplicationContext, pdfViewCtrl);
uiExtensionsManager.AttachedActivity = this;
uiExtensionsManager.OnCreate(this, pdfViewCtrl, savedInstanceState);
pdfViewCtrl.UIExtensionsManager = uiExtensionsManager;

c)打开和渲染一个PDF文档,设置内容视图。调用uiExtensionsManager.OpenDocument()函数打开和渲染PDF文档,而不是调用PDFViewCtrl.openDoc()函数。

uiExtensionsManager.OpenDocument(path, null);

SetContentView(uiExtensionsManager.ContentView);

d)添加ConfigurationChanges = ConfigChanges.KeyboardHidden|ConfigChanges.Orientation|ConfigChanges.ScreenSize 属性,以确保在旋转屏幕时只执行onConfigurationChanged()函数,而不会重新调用activity生命周期。如果不添加,签名功能可能无法正常使用。

[Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.KeyboardHidden|ConfigChanges.Orientation|ConfigChanges.ScreenSize, Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]

更新整个MainActivity.cs文件,如下所示:

备注添加Activity生命周期事件,否则某些功能可能无法正常使用。

using System;
using Android.App;
using Android.OS;
using Android.Runtime;

using Android;
using Android.Content.PM;
using AndroidX.AppCompat.App;
using AndroidX.Core.Content;
using AndroidX.Core.App;

using Com.Foxit.Sdk;
using Com.Foxit.Sdk.Common;
using Android.Content;
using Com.Foxit.Uiextensions;
using Android.Views;
using Android.Content.Res;

namespace TestXamarin

{

    [Activity(Label = "@string/app_name", ConfigurationChanges = ConfigChanges.KeyboardHidden|ConfigChanges.Orientation|ConfigChanges.ScreenSize, Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        public static int REQUEST_EXTERNAL_STORAGE = 1;
        private static string[] PERMISSIONS_STORAGE = {
            Manifest.Permission.ReadExternalStorage,
            Manifest.Permission.WriteExternalStorage
        };

        // The value of "sn" can be found in the "rdk_sn.txt".
        // The value of "key" can be found in the "rdk_key.txt".
        private String sn = "xxx";
        private String key = "xxx";

        private String path = "/mnt/sdcard/complete_pdf_viewer_guide_android.pdf";

        private PDFViewCtrl pdfViewCtrl;
        private UIExtensionsManager uiExtensionsManager;

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            // Initialize Foxit SDK Library.
            int errCode = Library.Initialize(sn, key);
            if (errCode != Constants.EErrSuccess)
                return;

            // Turn off the title at the top of the screen.
            RequestWindowFeature(WindowFeatures.NoTitle);

            // Set the window to Fullscreen.
            Window.SetFlags(WindowManagerFlags.Fullscreen, WindowManagerFlags.Fullscreen);
            // Instantiate a PDFViewCtrl object.
            pdfViewCtrl = new PDFViewCtrl(this.ApplicationContext);

            // Set the associated activity for RMS UI operations.
            pdfViewCtrl.AttachedActivity = this;

            // Initialize a UIExtensionManager object and set it to PDFViewCtrl.
            uiExtensionsManager = new UIExtensionsManager(this.ApplicationContext, pdfViewCtrl);
            uiExtensionsManager.AttachedActivity = this;
            uiExtensionsManager.OnCreate(this, pdfViewCtrl, savedInstanceState);
            pdfViewCtrl.UIExtensionsManager = uiExtensionsManager;

            // Require the authorization of runtime permissions.
            if (Build.VERSION.SdkInt > BuildVersionCodes.M)
            {
                Permission permission = ContextCompat.CheckSelfPermission(this.ApplicationContext, Manifest.Permission.WriteExternalStorage);
                if (permission != Permission.Granted)
                {
                    ActivityCompat.RequestPermissions(this, PERMISSIONS_STORAGE, REQUEST_EXTERNAL_STORAGE);
                    return;
                }
            }

            // Open and Render a PDF document.
            uiExtensionsManager.OpenDocument(path, null);
            SetContentView(uiExtensionsManager.ContentView);
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Permission[] grantResults)
        {
            if (requestCode == REQUEST_EXTERNAL_STORAGE
             && grantResults[0] == Permission.Granted)
            {
                if (uiExtensionsManager != null)
                {
                    uiExtensionsManager.OpenDocument(path, null);
                    SetContentView(uiExtensionsManager.ContentView);
                }
            }
            else
            {
                base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
            }
        }

        // Used for opening a RMS document.
        protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
        {
            pdfViewCtrl.HandleActivityResult(requestCode, (int)resultCode, data);
        }

        protected override void OnStart()
        {
            if (uiExtensionsManager != null)
            {
                uiExtensionsManager.OnStart(this);
            }
            base.OnStart();
        }

        protected override void OnStop()
        {
            if (uiExtensionsManager != null)
            {
                uiExtensionsManager.OnStop(this);
            }
            base.OnStop();
        }

        protected override void OnPause()
        {
            if (uiExtensionsManager != null)
            {
                uiExtensionsManager.OnPause(this);
            }
            base.OnPause();
        }

        protected override void OnResume()
        {
            if (uiExtensionsManager != null)
            {
                uiExtensionsManager.OnResume(this);
            }
            base.OnResume();
        }

        protected override void OnDestroy()
        {
            if (uiExtensionsManager != null)
            {
                uiExtensionsManager.OnDestroy(this);
            }
            base.OnDestroy();
        }

        public override void OnConfigurationChanged(Configuration newConfig)
        {
            base.OnConfigurationChanged(newConfig);
            if (uiExtensionsManager != null)
            {
                uiExtensionsManager.OnConfigurationChanged(this, newConfig);
            }
        }

        public override bool OnKeyDown([GeneratedEnum] Keycode keyCode, KeyEvent e)
        {
            if (uiExtensionsManager != null && uiExtensionsManager.OnKeyDown(this, (int)keyCode, e)) return true;
            return base.OnKeyDown(keyCode, e);
        }
    }
}

3)开启Multi-Dex. 如果不开启,您可能会遇到 “error MSB6006: “java.exe” exited with code 2.”的错误。

右击TestXamarin工程,选择Properties,在Android Options -> Packaging properties选项卡下勾选Enable Multi-Dex (见Figure 9-17)。

Figure 9-17

运行工程

当编译完项目并在模拟器上安装APK后,在弹出的窗口点击”Allow” 允许工程访问设备上的文件。然后您将看到 “complete_pdf_viewer_guide_android.pdf” 文档显示如Figure 9-18所示。到目前为止,该工程是一个功能齐全的PDF阅读器,包含Foxit PDF SDK for Android中的所有功能,并且支持打开RMS加密的文档。

Figure 9-18

自定义UI

关于如何在您的Xamarin Android工程中自定义UI,请参阅第4章 “自定义UI“。

Xamarin Android相关的FAQ

1)如何修复 “java.lang.OutOfMemoryError. Consider increasing the value of $(JavaMaximumHeapSize).” 错误?

当您在运行工程时遇到如上的错误,您可以尝试如下的方法。

在Visual Studio中,右键点击您的工程,选择Properties,在Android Options选项卡页面中点击Advanced,然后将 “Java Max Heap Size” 设置为一个更大的数值,例如,见Figure 9-19所示。

Figure 9-19

2)如何修复 “Java.Lang.IllegalStateException: This app has been built with an incorrect configuration. Please configure your build for VectorDrawableCompat” 异常?

该异常发生在当工程无法找到Android.Xamarin.Vector.Drawable NuGet包时。通常情况下,在Android Studio的 “build.gradle”中会进行设置,但是在使用Xamarin时,您需要在Visual Studio中安装此包。右击工程,选择 ‘Manage NuGet Packages…’。在NuGet Package Manager窗口,搜索’Xamarin.Android.Support.Vector.Drawable’,然后进行安装,如Figure 9-20所示。

Figure 9-20

备注工程target API需要设置为28以上您需要下载与您工程target API兼容的NuGet

FAQ

从指定的PDF文件路径打开一个PDF文档

如何从指定的PDF文件路径打开一个PDF文档?

Foxit PDF SDK for Android提供了多个接口用来打开PDF文档。您可以从指定的PDF文件路径或从内存缓冲区打开一个PDF文档。对于指定的PDF文件路径,有两种方法可以使用。

第一种是使用openDoc接口,该接口包括以下的操作:创建PDF文档对象(PDFDoc(String path)),加载文档内容(load),以及将PDF文档对象设置给视图控件(setDoc)。以下是示例代码:

备注openDoc接口仅可用于从文件路径打开PDF文档。如果需要自定义加载PDF文档,可以在回调函数 (FileRead) 中实现,然后使用带有回调函数FireRead PDFDoc (FileRead fileRead) 接口创建文档对象。接下来,使用load加载文档内容,并使用setDocPDF文档对象设置给视图控件。

// Assuming A PDFViewCtrl has been created.
// Open an unencrypted PDF document from a specified PDF file path.
String path = "/mnt/sdcard/input_files/Sample.pdf"; 
pdfViewCtrl.openDoc(path, null);

第二种是使用PDFDoc(String path) 接口创建PDF文档对象,使用load 接口加载文档内容,然后使用setDoc将PDF文档对象设置给视图控件。以下是示例代码:

// Assuming A PDFViewCtrl has been created.
String path = "/mnt/sdcard/input_files/Sample.pdf"; 
try {
       // Initialize a PDFDoc object with the path to the PDF file.
       PDFDoc document = new PDFDoc(path);

       // Load the unencrypted document content.
       document.load(null);

       // Set the document to view control.
       pdfViewCtrl.setDoc(document);
} catch (Exception e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
}

打开PDF文档时显示指定的页面

如何在打开PDF文档时,显示指定的页面?

为了在打开PDF文档时显示指定的页面,您需要使用接口gotoPage (int pageIndex)。Foxit PDF SDK for Android使用多线程来提高渲染速度,因此您需要确保在使用gotoPage接口之前,文档已经被成功加载。

请在IDocEventListener中实现回调接口,然后在onDocOpened事件中调用gotoPage接口。以下是示例代码:

// Assuming A PDFViewCtrl has been created.
// Register the PDF document event listener.
pdfViewCtrl.registerDocEventListener(docListener); 

// Open an unencrypted PDF document from a specified PDF file path.
String path = "/mnt/sdcard/input_files/Sample.pdf"; 
pdfViewCtrl.openDoc(path, null);

...

PDFViewCtrl.IDocEventListener docListener = new PDFViewCtrl.IDocEventListener() {
        @Override
        public void onDocWillOpen() {}

        @Override
        public void onDocOpened(PDFDoc pdfDoc, int errCode) {
            pdfViewCtrl.gotoPage(2);
        }

        @Override
        public void onDocWillClose(PDFDoc pdfDoc) { }

        @Override
        public void onDocClosed(PDFDoc pdfDoc, int i) { }

        @Override
        public void onDocWillSave(PDFDoc pdfDoc) { }

        @Override
        public void onDocSaved(PDFDoc pdfDoc, int i) { }

    };

License key和序列号无法正常工作

从网站下载的SDK包,未进行任何更改,为什么license key和序列号无法正常工作?

通常,上传到网站的包,里面的license key和序列号是可以正常工作的。在上传到网站之前是经过测试的。因此,如果您发现license key和序列号无法使用,则可能是由设备的日期引起的。如果您设备的时间在下载包 “libs” 文件夹下rdk_key.txt文件中的StartDate之前,则 “librdk.so” 库将无法解锁。请检查您设备的日期。

在PDF文档中添加link注释

如何在PDF文档中添加link注释?

为了将link注释添加到PDF文档,首先需要调用PDFPage.addAnnot将一个link注释添加到指定页面,然后调用Action.Create创建一个action,并将该action设置给刚添加的link注释。以下是在PDF首页添加一个URI link注释的示例代码:

private Link linkAnnot = null;


...


String path = "mnt/sdcard/input_files/sample.pdf";
try {

    // Initialize a PDFDoc object with the path to the PDF file.
    PDFDoc document = new PDFDoc(path);

    // Load the unencrypted document content.
    document.load(null);

    // Get the first page of the PDF file.
    PDFPage page = document.getPage(0);

    // Add a link annotation to the first page.
    linkAnnot = new Link (page.addAnnot(Annot.e_Link, new RectF(250, 650, 400, 750)));

    // Create a URI action and set the URI.
    URIAction uriAction = new URIAction(Action.create(document, Action.e_TypeURI));
    uriAction.setURI("www.foxitsoftware.com");
    // Set the action to link annotation.
    linkAnnot.setAction(uriAction);

    // Reset appearance stream.
    linkAnnot.resetAppearanceStream();

    // Save the document that has added the link annotation.
    document.saveAs("mnt/sdcard/input_files/sample_annot.pdf", PDFDoc.e_SaveFlagNormal);
    
} catch (Exception e) {
    e.printStackTrace();
}

向PDF文档中插入图片

如何PDF文档中插入图片?

有两种方法可以帮助您将图片插入到PDF文档中。第一钟是调用PDFPage.addImageFromFilePath接口。您可以参考如下示例代码,该代码将图片插入到PDF文档的首页:

备注:在调用PDFPage.addImageFromFilePath接口之前,您需要获取并解析将要添加图片的页面。

String path = "mnt/sdcard/input_files/sample.pdf";
try {

// Initialize a PDFDoc object with the path to the PDF file.
PDFDoc document = new PDFDoc(path);

// Load the unencrypted document content.
document.load(null);

// Get the first page of the PDF file.
PDFPage page = document.getPage(0);

// Parse the page.
if (!page.isParsed()) {
Progressive parse = page.startParse(e_ParsePageNormal, null, false);
 int state = Progressive.e_ToBeContinued;
while (state == Progressive.e_ToBeContinued) {
state = parse.resume();
}
}

// Add an image to the first page.
page.addImageFromFilePath("mnt/sdcard/input_files/2.png", new PointF(20, 30), 60, 50, true);

// Save the document that has added the image.
document.saveAs("mnt/sdcard/input_files/sample_image.pdf", PDFDoc.e_SaveFlagNormal);

} catch (Exception e) {
e.printStackTrace();
}

第二种是使用PDFPage.addAnnot接口在指定的页面添加一个stamp,然后将图片转换为位图,并将位图设置给刚添加的stamp注释。您可以参考以下的示例代码,该代码将图片作为stamp注释插入到PDF文件的首页:

String path = "mnt/sdcard/input_files/sample.pdf";
try {

    // Initialize a PDFDoc object with the path to the PDF file.
    PDFDoc document = new PDFDoc(path);

    // Load the unencrypted document content.
    document.load(null);

    // Get the first page of the PDF file.
    PDFPage page = document.getPage(0);

    // Add a stamp annotation to the first page.
    Stamp stamp = new Stamp(page.addAnnot(Annot.e_Stamp, new RectF(100, 350, 250, 150))); 

    // Load a local image and convert it to a Bitmap.
    Bitmap bitmap = BitmapFactory.decodeFile("mnt/sdcard/input_files/2.png");

    // Set the bitmap to the added stamp annotation.
    stamp.setBitmap(bitmap);

    //Reset appearance stream.
    stamp.resetAppearanceStream();

    // Save the document that has added the stamp annotation.
    document.saveAs("mnt/sdcard/input_files/sample_image.pdf", PDFDoc.e_SaveFlagNormal);

} catch (Exception e) {
    e.printStackTrace();
}

高亮PDF文档中的links和设置高亮颜色

如何设置是否高亮PDF文档中的links? 以及如何设置高亮的颜色?

默认情况下,高亮PDF文档中的links功能是启用的。如果您想要禁用它或者设置高亮颜色,可以通过JSON文件 (仅支持6.3及以上版本) 或调用API进行设置。

备注:如果您需要设置高亮的颜色,请确保links高亮的功能是启用的。

通过JSON文件

设置 ““highlightLink”: false,” 在PDF文档中禁用links高亮功能。

设置 ““highlightLinkColor”: “#16007000”,” 设置高亮颜色 (输入您需要的颜色值)。

通过调用API

UIExtensionsManager.enableLinkHighlight() 接口用来设置是否在PDF文档中启用links高亮的功能。如果您不需要启用该功能,请将其参数设置为 “false”,如下所示:

// Assume you have already Initialized a UIExtensionsManager object uiExtensionsManager.enableLinkHighlight(false);

UIExtensionsManager.setLinkHighlightColor() 接口用来设置高亮的颜色。以下是调用此API的示例代码:

// Assume you have already Initialized a UIExtensionsManager object


uiExtensionsManager.setLinkHighlightColor(0x4b0000ff);

高亮PDF文档中的表单域和设置高亮颜色

如何设置是否高亮PDF文档中的表单域? 以及如何设置高亮的颜色

默认情况下,高亮PDF文档中的表单域功能是启用的。如果您想要禁用它或者设置高亮颜色,可以通过JSON文件 (仅支持6.3及以上版本) 或调用API进行设置。

备注如果您需要设置高亮的颜色,请确保表单域高亮的功能是启用的。

通过JSON文件

设置”“highlightForm”: false,” 在PDF文档中禁用表单域高亮功能。

设置”“highlightFormColor”: “#2000ffcc”,” 设置高亮颜色 (输入您需要的颜色值)。

通过调用API

UIExtensionsManager.enableFormHighlight()接口用来设置是否在PDF文档中启用表单域高亮的功能。如果您不需要启用该功能,请将其参数设置为 “false”,如下所示:

// Assume you have already Initialized a UIExtensionsManager object uiExtensionsManager.enableFormHighlight(false);

UIExtensionsManager.setFormHighlightColor() 接口用来设置高亮的颜色。以下是调用此API的示例代码:

// Set the highlight color to blue.


uiExtensionsManager.setFormHighlightColor(0x4b0000ff);

支持全文索引搜索

Foxit PDF SDK for Android是否支持全文索引搜索?如果支持,如何搜索在我的移动设备上离线存储的PDF文件?

是的。Foxit PDF SDK for Android从5.0版本开始就支持全文索引搜索。

要使用此功能,请按照如下的步骤:

a)根据目录来创建一个文档来源,该目录为文档的搜索目录。

public DocumentsSource(String directory)

b)创建一个全文文本搜索对象,以及设置用于存储索引数据的数据库路径。

public FullTextSearch()


public void setDataBasePath(String pathOfDataBase)

c)开始索引文档来源中的PDF文档。

public Progressive startUpdateIndex(DocumentsSource source,


        PauseCallback pause, boolean reUpdate)

备注:您可以索引指定的PDF文件例如,如果某个PDF文档的内容发生更改,您可以使用以下API对其重新进行索引:

public boolean updateIndexWithFilePath(java.lang.String filePath)

d)从索引数据源中搜索指定的内容。搜索的结果将通过指定的回调函数来返回给外部,每找到一个匹配结果,则调用一次回调函数。

public boolean searchOf(java.lang.String matchString,
        RankMode rankMode,
        SearchCallback searchCallback)

如下是使用全文索引搜索的示例代码:

String directory = "A search directory...";
FullTextSearch search = new FullTextSearch();
try {
    String dbPath = "The path of data base to store the indexed data...";
    search.setDataBasePath(dbPath);
    // Get document source information.
    DocumentsSource source = new DocumentsSource(directory);

    // Create a Pause callback object implemented by users to pause the updating process.
    PauseUtil pause = new PauseUtil(30);

    // Start to update the index of PDF files which receive from the source.
    Progressive progressive = search.startUpdateIndex(source, pause, false);
    int state = Progressive.e_ToBeContinued;
    while (state == Progressive.e_ToBeContinued) {
        state = progressive.resume();
    }

    // Create a callback object which will be invoked when a matched one is found.
    MySearchCallback searchCallback = new MySearchCallback();

    // Search the specified keyword from the indexed data source.
    search.searchOf("looking for this text", RankMode.e_RankHitCountASC, searchCallback);
} catch (PDFException e) {
    e.printStackTrace();
}

PauseUtil 回调的示例代码如下所示:

public class PauseUtil extends PauseCallback{
    private long m_milliseconds = 0;
    private long m_startTime = 0;

    public PauseUtil(long milliSeconds) {
        Date date = new Date();
        m_milliseconds = milliSeconds;
        m_startTime = date.getTime();
    }

    @Override
    public boolean needToPauseNow() {
        // TODO Auto-generated method stub
        if (this.m_milliseconds < 1)
            return false;
        Date date = new Date();
        long diff = date.getTime() - m_startTime;
        if (diff > this.m_milliseconds) {
            m_startTime = date.getTime();
            return true;
        } else
            return false;
    }
}

MySearchCallback回调的示例如下所示:

public class MySearchCallback extends SearchCallback {
    private static final String TAG = MySearchCallback.class.getCanonicalName();

    @Override
    public void release() {
    }

    @Override
    public int retrieveSearchResult(String filePath, int pageIndex, String matchResult, int matchStartTextIndex, int matchEndTextIndex) {
        String s = String.format("Found file is :%s \n Page index is :%d Start text index :%d End text index :%d \n Match is :%s \n\n", filePath, pageIndex, matchStartTextIndex, matchEndTextIndex, matchResult);
        Log.v(TAG, "retrieveSearchResult: " + s);
        return 0;
    }
}

备注

  • Foxit PDF SDK for Android提供的全文索引搜索将以递归的方式遍历整个目录,以便搜索目录下的文件和文件夹都会被索引。
  • 如果需要中止索引进程,可以将pause回调参数传递给startUpdateIndex接口回调函数needToPauseNow都会被调用,一旦完成一个PDF文档的索引。因此调用者可以在回调函数 needToPauseNow返回 true 时中止索引进程。
  • 索引数据库的位置由setDataBasePath接口设置。如果要清除索引数据库,请手动清除目前,不支持从索引函数中删除指定文件相关的索引内容
  • searchOf接口的每个搜索结果都由指定的回调返回到外部。一旦searchOf接口返回true false,则表示搜索已完成。

打印PDF文档

Foxit PDF SDK for Android是否支持打印PDF文档?如果支持,如何使用?

是的。Foxit PDF SDK for Android从5.1版本开始支持打印PDF文档的功能。您可以在Complete PDF viewer demo的More Menu View菜单中点击Wireless Print 按钮来打印PDF文档。此外,您可以调用以下API来打印PDF文档:

// for iPhone and iTouch

public void startPrintJob(Context context, PDFDoc doc, String printJobName, String outputFileName, IPrintResultCallback callback)

使用PDF打印功能的示例代码:

// Assume you have already Initialized a UIExtensionsManager object



PDFDoc doc = null;
IPrintResultCallback print_callback = new IPrintResultCallback() {
    @Override
    public void printFinished() {
    }

    @Override
    public void printFailed() {
    }

    @Override
    public void printCancelled() {
    }
};

try {
    doc = new PDFDoc("/mnt/sdcard/input_files/Sample.pdf");
    doc.load(null);
} catch (PDFException e) {
    Assert.fail("unexpect a PDF Exception!!errCode = " + e.getLastError());
    }
uiExtensionsManager.startPrintJob(getActivity(), doc, "print with name", "print_withAPI", print_callback);
}

夜间模式颜色设置

如何设置夜间模式颜色?

6.3版本开始,您可以在JSON配置文件中轻松地设置夜间模式的颜色,只需要设置以下两个配置项:

"mapForegroundColor": "#000333",
"mapBackgroundColor": "#fff666",

从5.1版本开始,设置夜间模式颜色,需要首先调用PDFViewCtrl.setMappingModeBackgroundColor(int)PDFViewCtrl.setMappingModeForegroundColor(int) 接口根据您的需要设置颜色,然后使用PDFViewCtrl.setColorMode(int) 设置颜色模式。

备注:如果颜色模式已经设置为Renderer.e_ColorModeMapping,在调用PDFViewCtrl.setMappingModeBackgroundColor(int) PDFViewCtrl.setMappingModeForegroundColor(int) 接口之后,您仍然需要再次设置否则,颜色设置可能不会起作用。

设置夜间模式颜色的示例代码:

private UIExtensionsManager uiExtensionsManager = null;
...
PDFViewCtrl pdfViewCtrl = uiExtensionsManager.getPDFViewCtrl();
pdfViewCtrl.setMappingModeBackgroundColor(0xff87cefa);
pdfViewCtrl.setMappingModeForegroundColor(0xff7cfc00);
pdfViewCtrl.setColorMode(Renderer.e_ColorModeMapping);

输出exception/crash日志信息

如何在应用程序抛出异常或者crash时,输出exception/crash日志信息?

setExceptionLogger 接口用来输出exception/crash日志信息,该接口引用了第三方xCrash库。其使用方法如下:

1)添加XCrash依赖。

dependencies {
    implementation 'com.iqiyi.xcrash:xcrash-android-lib:2.1.4'
}

2)指定一个或多个ABI。

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }
}

3)在代码中调用setExceptionLogger

public class MainApplication extends Application {
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        PDFViewCtrl.setExceptionLogger(this, "/mnt/sdcard/FoxitSDK/crash", new PDFViewCtrl.IExceptionLogger() {
            @Override
            public void onExceptionLogger(String filePath)



            { Log.d("", "onExceptionLogger: " + filePath); }
        });
    }
}

减小APK的大小

如何减小APK大小?

要减少APK的大小,您可以编译多个包含特定屏幕密度或者ABI文件的APK。有关多APK更详细的介绍,请参阅Build Multiple APKs。如下的代码片段是基于屏幕密度和ABI来配置多个APK,该代码将添加到模块级的build.gradle 文件中。

android {
 ...
    splits {
        // Configures multiple APKs based on screen density.
        density {
            // Configures multiple APKs based on screen density.
            enable true
            // Specifies a list of screen densities Gradle should not create multiple APKs for.
            exclude "ldpi", "xxhdpi", "xxxhdpi"
            // Specifies a list of compatible screen size settings for the manifest.
            compatibleScreens 'small', 'normal', 'large', 'xlarge'
        }
        // Configures multiple APKs based on ABI.
        abi {
            // Enables building multiple APKs per ABI.
            enable true
            // By default all ABIs are included, so use reset() and include to specify that we only
            // want APKs for x86 and x86_64.
            // Resets the list of ABIs that Gradle should create APKs for to none.
            reset()
            // Specifies a list of ABIs that Gradle should create APKs for.
            include "x86", "x86_64", "armeabi-v7a", "arm64-v8a"
            // Specifies that we do not want to also generate a universal APK that includes all ABIs.
            universalApk false
        }
    }
}

开启 shrink-code (设置 “minifyEnabled” 为 “true”)

当在App’s build.gradle中将 “minifyEnabled” 设置为 “true” 为什么在运行时会遇到一些异常?

当在App’s build.gradle中将 “minifyEnabled” 设置为 “true” 来开启shrink-code时,请注意您需要在proguard-rules.pro文件中添加如下的内容,否则在运行时会抛出异常。

proguard-rules.pro 文件中,添加如下的内容:

-dontwarn com.foxit.sdk.**
-keep class com.foxit.sdk.**{ *;}

-dontwarn com.microsoft.rightsmanagement.**
-keep class com.microsoft.rightsmanagement.** {*;}

-dontwarn com.microsoft.aad.adal.**
-keep class com.microsoft.aad.adal.** {*;}

-dontwarn com.edmodo.cropper.**
-keep class com.edmodo.cropper.** {*;}

-dontwarn org.bouncycastle**
-keep class org.bouncycastle.** {*;}

本地化设置

如何进行本地化设置?

默认情况下,Foxit PDF SDK for Android会根据您系统的当前语言自动切换UI语言,前提是Foxit PDF SDK for Android支持该语言。

当前,Foxit PDF SDK for Android 支持如下的语言:英语(English),德语(German, de-CH, de-DE),拉丁语(Latin, es-LA),法语(Frence, fr-FR),意大利语(Italian, it-IT),韩语(Korean, ko),荷兰语(Dutch, ni-NL),葡萄牙语(Portuguese, pt-BR),俄语(Russian, ru-Ru) 和 中文(Chinese, zh-CN, zh-TW)。这些语言的资源文件位于 “libs\uiextensions_src\src\main\res” 文件夹下。

如果您需要使用自己本地的语言 (而该语言是Foxit PDF SDK for Android当前不支持的语言),您需要首先将UI上面所有的字段都翻译成本地的语言,其次将翻译后的资源文件放入您工程中其他语言资源文件所在的同一目录中。然后,修改您系统的当前语言为您本地的语言,或者调用Localization.setCurrentLanguage接口使其生效。有关Localization.setCurrentLanguage接口更详细的说明,请参阅 “doc” 目录下的API Reference。

例如,假如您需要将”complete_pdf_viewer” demo的UI语言改为,您可以按照如下的步骤:

a)将”libs\uiextensions_src\src\main\res”目录下的 “values” (以此举例) 拷贝到demo的资源目录 “samples\complete_pdf_viewer\app\src\main\res”,并将其重命名为”values-ja-rJA“。

b)翻译 (本地化) “values-ja-rJA” 文件夹下XML文件的所有字段。

c)然后,采用如下两种方法中的任意一种使本地化语言生效:

  • 将系统的当前语言修改为日文。
  • 调用Localization.setCurrentLanguage 接口将当前语言设置为日文。
Locale locale = new Locale("ja","JA");
Localization.setCurrentLanguage(this.getContext(), locale);

迁移到AndroidX

如何Foxit PDF SDK for Android迁移AndroidX

从7.2版本开始,Foxit PDF SDK for Android只支持AndroidX,不再支持Android support 库。因此,如果您当前使用的是7.2版本之前的SDK,并且您需要升级SDK以便使用最新发布中的相关新功能,那么您需要将Foxit PDF SDK for Android迁移到AndroidX。

迁移要求

Android Studio version >= 3.2.0

Gradle Build Tool >= 3.2.0

Gradle Version >= 4.6

compileSdkVersion >= 28

迁移

关于迁移,您可以参阅官方迁移文档。在本教程中,我们将以 “samples” 文件夹下的complete_pdf_viewer demo为例来说明如何迁移到AndroidX。

1)修改工程级的 “gradle.properties”。在 “gradle.properties” 文件中添加如下的内容: android.useAndroidX=true android.enableJetifier=true

2)在app’s “build.gradle” 中修改相关的依赖。

a)将compileSdkVersion设置为28或以上:

b)将工程级 “build.gradle” 中的 ‘com.android.tools.build:gradle‘ 升级到 3.2.0或更高版本。

c)点击Refactor > Migrate to AndroidX开始迁移。

d)升级第三方库。如果工程中的第三方库引用了Android support相关的库,那么第三方库也需要迁移到AndroidX。

升级前,app’s “build.gradle” 中的dependencies 如下所示:

dependencies { implementation 'com.android.support:appcompat-v7:27.1.1' 
implementation 'com.android.support:design:27.1.1' 
implementation 'com.android.support:multidex:1.0.+' 
implementation(name: 'FoxitRDK', ext: 'aar') 
implementation(name: 'FoxitRDKUIExtensions', ext: 'aar') 
implementation 'com.edmodo:cropper:1.0.1' 
implementation('com.microsoft.aad:adal:1.1.16') {} 
implementation(name: 'RMSSDK-4.2-release', ext: 'aar') 
implementation(name: 'rms-sdk-ui', ext: 'aar') // RxJava 
implementation "io.reactivex.rxjava2:rxjava:2.2.2" 
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' 
implementation 'org.bouncycastle:bcpkix-jdk15on:1.60' 
implementation 'org.bouncycastle:bcprov-jdk15on:1.60' }

升级后,app’s “build.gradle” 中的dependencies 如下所示:

dependencies { 
implementation 'androidx.appcompat:appcompat:1.1.0' 
implementation 'com.google.android.material:material:1.1.0' 
implementation 'androidx.multidex:multidex:2.0.1' 
implementation(name: 'FoxitRDK', ext: 'aar') 
implementation(name: 'FoxitRDKUIExtensions', ext: 'aar') 
implementation 'com.edmodo:cropper:1.0.1' 
implementation('com.microsoft.aad:adal:1.16.3') {} 
implementation(name: 'RMSSDK-4.2-release', ext: 'aar') 
implementation(name: 'rms-sdk-ui', ext: 'aar') // RxJava 
implementation "io.reactivex.rxjava2:rxjava:2.2.16" 
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' 
implementation 'org.bouncycastle:bcpkix-jdk15on:1.64' 
implementation 'org.bouncycastle:bcprov-jdk15on:1.64' }

迁移后,您可以需要手动修改某些包名。因为,在自动迁移的过程中,有些包可能无法正确导入。另外,您可能遇到一些其他的问题,在这种情况下,您需要根据具体的情况来解决相关的问题。

 

技术支持

问题报告

Foxit为其产品提供全天候24小时支持,并拥有PDF行业优秀的技术支持工程师开发团队。如果您在使用Foxit PDF SDK for Android时遇到任何技术问题或bug,请在http://tickets.foxitsoftware.com/create.php 网页上将问题报告提交给Foxit技术支持团队。为了更好地帮助您解决问题,请提供以下信息:

  • 联系方式
  • Foxit PDF SDK产品和版本
  • 您使用的操作系统和IDE版本
  • 问题的详细说明
  • 任何其他相关信息,例如日志文件或错误信息截图

联系方式

您可以直接联系Foxit,请使用以下的联系方式:

线上支持

http://www.foxitsoftware.com/support/

联系销售:

电话: 1-866-680-3668

邮箱:sales@foxitsoftware.com

联系技术支持团队:

电话: 1-866-MYFOXIT or 1-866-693-6948

邮箱: support@foxitsoftware.com

更新于 2020年10月26日

这篇文章有用吗?

相关文章