Skip to content

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

福昕 PDF SDK 鸿蒙版将所有的 UI 实现(包括应用程序的基本 UI 和即用型 UI 功能模块)封装在 UI Extensions 组件中,因此开发人员可以轻松快速通过几行代码构建一个功能丰富的 PDF 阅读器。本章将提供详细的教程来帮助您快速开始使用福昕 PDF SDK 鸿蒙版在 HarmonyOS 平台创建 PDF 阅读器。

如果您正在基于 OpenHarmony 操作系统开发应用,以下示例工程的步骤同样适用。

提示:

OpenHarmony 上预置 UIExtensions HAR 为试验性支持(详见 入门指南)。若暂不使用 UIExtensions,可忽略示例工程中所有与 UIExtensions 组件相关的步骤。

创建一个新的示例工程 ​

以下将创建一个名为 PDFReader 的示例工程。构建的示例工程环境,使用较新的 DevEco Studio,以及 HarmonyOS / OpenHarmony SDK API Version 12 及以上。

  1. 打开 DevEco Studio,创建新工程:

    • 选择 File -> New -> Create Project…。
    • 在 Choose Your Ability Template 向导中,选择 Empty Ability。
    • 点击 Next。

    create_project.png

  2. 配置工程信息:

    • 在 Configure Your Project 对话框中,填写工程相关信息,例如工程名称、存储路径等。
    • 点击 Finish。

    configure_project.png

集成 SDK 到示例工程 ​

我们将集成 SDK 默认的内置 UI 到示例工程。 为了简单和方便,本示例工程将直接使用 UI Extensions 组件,而非源代码工程。 我们只需要添加以下的库文件到 PDFReader 示例工程中。

  • FoxitRDK.har
  • FoxitRDKUIExtensions.har

操作步骤:

  1. 拷贝库文件夹

    • 将下载包中的 libs 文件夹拷贝到工程根目录,即 PDFReader 文件夹下。
  2. 安装 FoxitRDK.har

    • 打开 Terminal,在工程根目录下,运行以下命令来安装 FoxitRDK.har:
    ts
    ohpm install libs/FoxitRDK.har
    

    安装成功后,

    • 在 PDFReader/oh_modules 目录下会生成 foxit\_rdk 文件夹。
    • 在 PDFReader/oh-package.json5 文件中会自动生成引用,如下图所示。

    install_foxit_rdk_result.png

  3. 安装 FoxitRDKUIExtensions.har 库。

    • 由于 FoxitRDKUIExtensions.har 依赖 FoxitRDK.har,因此当在本地环境安装时,您需要在 PDFReader/oh-package.json5 文件中添加如下的命令:
    ts
    "overrides": {
      "foxit_rdk": "file:libs/FoxitRDK.har"
    },
    
    • 打开 Terminal,在工程根目录下,运行以下命令来安装 FoxitRDKUIExtensions.har:
    ts
    ohpm install libs/FoxitRDKUIExtensions.har
    

    安装成功后,

    • 在 PDFReader/oh_modules 目录下会生成 uiextensions 文件夹。
    • 在 PDFReader/oh-package.json5 文件中会自动生成引用,如下图所示。

    install_uiextensions.png

初始化 SDK ​

请参看“授权并初始化 SDK”。

添加 PDF 文件到沙盒目录 ​

PDF 文件的管理由应用层自己实现。应用层可以创建一个文件列表来管理文件,详细信息可参考鸿蒙系统的接口手册。本示例为了简单起见,将 PDF 文件拷贝到沙盒目录下。 操作步骤:

  1. 将 PDF 文件,比如 "Sample.pdf" 拷贝到 "PDFReader/entry/src/main/resources/rawfile" 文件夹下。
  2. 在 "PDFReader/entry/src/main/ets/pages/Index.ets" 文件中添加如下的代码:
[Index.ets]
ts
import fs from '@ohos.file.fs';

let context = getContext(this)
let filesDir = context.filesDir;

let fileName = "Sample.pdf";
let openDocPath = filesDir + "/" + fileName;

export async function copy_rawfile__to_sandbox(cb: (error: Error) => void) {
    try {

        let sss = fs.createStreamSync(openDocPath, "w");
        sss.closeSync();

        / 获取 rawfile 目录下的 Sample.pdf
        context.resourceManager.getRawFd('rawfile/' + fileName, async (error, value) => {

            if (error != null) {  / getRawFileDescriptor 运行失败
                console.log("[rawfile_copy_to_sandbox] 未能成功将rawfile目录下的Sample.pdf文件拷贝到应用沙盒目录下");
            } else {             / getRawFileDescriptor 运行成功
                let fd = value.fd;
                console.log("LXG fd:" + fd)
                console.log("LXG fileDir:" + filesDir)
                / 打开文件流
                let inputStream = fs.fdopenStreamSync(fd, "r+");
                console.log("LXG inputStream:")
                let outputStream = fs.createStreamSync(openDocPath, "w+");
                / 以流的形式读取源文件内容并写入目标文件
                let bufSize = value.length;
                let readSize = 0;
                let buf = new ArrayBuffer(bufSize);

                class Option {
                    public offset: number = 0;
                    public length: number = bufSize;
                }

                let option = new Option();
                option.offset = value.offset;
                let readLen = await inputStream.read(buf, option);
                readSize += readLen;
                while (readLen > 0) {
                    await outputStream.write(buf);
                    option.offset = readSize + value.offset;
                    option.length = value.length - readSize >= bufSize ? bufSize : value.length - readSize;
                    readLen = await inputStream.read(buf, option);
                    readSize += readLen;
                }
                / 关闭文件流
                inputStream.closeSync();
                outputStream.closeSync();
            }
            return cb(error);
        });

    } catch (error) {
        console.log("[rawfile_copy_to_sandbox] 未能成功将 rawfile 目录下的 Sample.pdf 文件拷贝到应用沙盒目录下");
        return cb(error);
    }
    return cb(new Error());
}

使用 PDFViewCtrl 组件显示 PDF 文档 ​

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

操作步骤:

  • 在 "PDFReader/entry/src/main/ets/pages/Index.ets" 文件中添加如下的代码:
[Index.ets]
ts
@Entry
@Component
struct
Index
{
@State
    message
:
    string = 'Open Doc';
@State
    clickID
:
    number = 0;
@State
    pdfViewCtrlModel
:
    PDFViewCtrlModel = new PDFViewCtrlModel(getContext(this))
public
    openDoc()
    {
        this.pdfViewCtrlModel.openDoc(openDocPath, '', (error) => {
            if (error == FoxitRDKNative.common.e_ErrSuccess) {
                this.clickID = 1;
            }
        })
    }
    build()
    {
        Row()
        {
            Column()
            {
                if (this.clickID == 0) {
                    Button(this.message)
                        .fontSize(50)
                        .fontWeight(FontWeight.Bold)
                        .onClick(() => {
                            let res = fs.accessSync(openDocPath);
                            if (!res) {
                                copy_rawfile__to_sandbox((error) => {
                                    if (error == null) {
                                        try {
                                            this.openDoc()
                                        } catch (err) {
                                            console.info(` fail callback, code: ${err.code}, msg: ${err.msg}`)
                                        }
                                    }
                                })
                            } else {
                                this.openDoc()
                            }
                        })
                } else {
                    PDFViewCtrl({model: this.pdfViewCtrlModel})
                        .width('100%')
                        .height('100%')
                        .backgroundColor(Color.Black)
                }
            }
        .
            width('100%')
        }
    .
        height('100%')
    }
}

当前,"PDFReader/entry/src/main/ets/pages/Index.ets" 的全部代码如下所示:

[Index.ets]
ts
import {FoxitRDKNative, PDFViewCtrl, PDFViewCtrlModel,} from 'foxit_rdk';
import fs from '@ohos.file.fs';

// 初始化SDK库
let sn: string = ''
let key: string = ''
FoxitRDKNative.common.Library.Initialize(sn, key);

// 将PDF文档拷贝到应用沙盒目录下
let context = getContext(this)
let filesDir = context.filesDir;

let fileName = "sample.pdf";
let openDocPath = filesDir + "/" + fileName;

export async function copy_rawfile__to_sandbox(cb: (error: Error) => void) {
    try {

        let sss = fs.createStreamSync(openDocPath, "w");
        sss.closeSync();

        / 获取rawfile目录下的 "sample.pdf"
        context.resourceManager.getRawFd('rawfile/' + fileName, async (error, value) => {

            if (error != null) {  / getRawFileDescriptor运行失败
                console.log("[rawfile_copy_to_sandbox] 未能成功将rawfile目录下的 sample.pdf文件拷贝到应用沙盒目录下");
            } else {             / getRawFileDescriptor运行成功
                let fd = value.fd;
                console.log("LXG fd:" + fd)
                console.log("LXG fileDir:" + filesDir)
                / 打开文件流
                let inputStream = fs.fdopenStreamSync(fd, "r+");
                console.log("LXG inputStream:")
                let outputStream = fs.createStreamSync(openDocPath, "w+");
                / 以流的形式读取源文件内容并写入目标文件
                let bufSize = value.length;
                let readSize = 0;
                let buf = new ArrayBuffer(bufSize);

                class Option {
                    public offset: number = 0;
                    public length: number = bufSize;
                }

                let option = new Option();
                option.offset = value.offset;
                let readLen = await inputStream.read(buf, option);
                readSize += readLen;
                while (readLen > 0) {
                    await outputStream.write(buf);
                    option.offset = readSize + value.offset;
                    option.length = value.length - readSize >= bufSize ? bufSize : value.length - readSize;
                    readLen = await inputStream.read(buf, option);
                    readSize += readLen;
                }
                / 关闭文件流
                inputStream.closeSync();
                outputStream.closeSync();
            }
            return cb(error);
        });

    } catch (error) {
        console.log("[rawfile_copy_to_sandbox] 未能成功将rawfile目录下的 sample.pdf文件拷贝到应用沙盒目录下");
        return cb(error);
    }
    return cb(new Error());
}

// 使用PDFViewCtrl打开一个PDF文档
@Entry
@Component
struct
Index
{
@State
    message
:
    string = 'Open Doc';
@State
    clickID
:
    number = 0;
@State
    pdfViewCtrlModel
:
    PDFViewCtrlModel = new PDFViewCtrlModel(getContext(this))
public
    openDoc()
    {
        this.pdfViewCtrlModel.openDoc(openDocPath, '', (error) => {
            if (error == FoxitRDKNative.common.e_ErrSuccess) {
                this.clickID = 1;
            }
        })
    }
    build()
    {
        Row()
        {
            Column()
            {
                if (this.clickID == 0) {
                    Button(this.message)
                        .fontSize(50)
                        .fontWeight(FontWeight.Bold)
                        .onClick(() => {
                            let res = fs.accessSync(openDocPath);
                            if (!res) {
                                copy_rawfile__to_sandbox((error) => {
                                    if (error == null) {
                                        try {
                                            this.openDoc()
                                        } catch (err) {
                                            console.info(` fail callback, code: ${err.code}, msg: ${err.msg}`)
                                        }
                                    }
                                })
                            } else {
                                this.openDoc()
                            }
                        })
                } else {
                    PDFViewCtrl({model: this.pdfViewCtrlModel})
                        .width('100%')
                        .height('100%')
                        .backgroundColor(Color.Black)
                }
            }
        .
            width('100%')
        }
    .
        height('100%')
    }
}

如果您使用华为模拟器来运行该示例工程。运行成功后,点击 OpenDoc 按钮,即可看到 Sample.pdf 文档显示如下图所示 。

提示

  • PDFViewCtrl 是视图控制组件,仅提供一些基本功能, 比如,显示 PDF 文档、放大、缩小,及翻页等。

open_doc_1_result.png

使用 UI Extensions 组件构建一个功能丰富的 PDF 阅读器 ​

福昕 PDF SDK 鸿蒙版提供内置的 UI 设计,包含应用程序基础 UI 和功能模块 UI。这些内置 UI 通过 SDK 鸿蒙版实现,并封装在 UI Extensions 组件中。因此,构建功能完善的 PDF 阅读器变得非常简单,您只需实例化一个 UIExtensionsManager 对象。

操作步骤:

  • 在 "PDFReader/entry/src/main/ets/pages/Index.ets" 文件中添加如下的代码:
[Index.ets]
ts
@Entry
@Component
struct
Index
{
@State
    message
:
    string = 'Open Doc';
@State
    clickID
:
    number = 0;
@State
    uiextensionsManager
:
    UIExtensionsManager = new UIExtensionsManager(getContext(this));
public
    openDoc()
    {
        this.uiextensionsManager.openDocument(openDocPath, '', (error: number) => {
            if (error == FoxitRDKNative.common.e_ErrSuccess) {
                this.clickID = 1;
            }
        })
    }

    build()
    {
        Row()
        {
            Column()
            {
                if (this.clickID == 0) {
                    Button(this.message)
                        .fontSize(50)
                        .fontWeight(FontWeight.Bold)
                        .onClick(() => {
                            let res = fs.accessSync(openDocPath);
                            if (!res) {
                                copy_rawfile__to_sandbox((error) => {
                                    if (error == null) {
                                        try {
                                            this.openDoc()
                                        } catch (err) {
                                            console.info(` fail callback, code: ${err.code}, msg: ${err.msg}`)
                                        }
                                    }
                                })
                            } else {
                                this.openDoc()
                            }
                        })
                } else {
                    UIExtensionsComponent({uiExtMgr: this.uiextensionsManager})
                        .width('100%')
                        .height('100%')
                        .backgroundColor(Color.Black)
                }
            }
        .
            width('100%')
        }
    .
        height('100%')
    }
}

当前,"PDFReader/entry/src/main/ets/pages/Index.ets" 的全部代码如下所示:

[Index.ets]
ts
import {FoxitRDKNative, PDFViewCtrl, PDFViewCtrlModel,} from 'foxit_rdk';
import {UIExtensionsManager, UIExtensionsComponent} from 'uiextensions';

import fs from '@ohos.file.fs';

// 初始化SDK库
let sn: string = ''
let key: string = ''

FoxitRDKNative.common.Library.Initialize(sn, key);

// 将PDF文档拷贝到应用沙盒目录下
let context = getContext(this)
let filesDir = context.filesDir;

let fileName = "sample.pdf";
let openDocPath = filesDir + "/" + fileName;

export async function copy_rawfile__to_sandbox(cb: (error: Error) => void) {
    try {

        let sss = fs.createStreamSync(openDocPath, "w");
        sss.closeSync();

        / 获取rawfile目录下的 "sample.pdf"
        context.resourceManager.getRawFd('rawfile/' + fileName, async (error, value) => {

            if (error != null) {  / getRawFileDescriptor运行失败
                console.log("[rawfile_copy_to_sandbox] 未能成功将rawfile目录下的 sample.pdf文件拷贝到应用沙盒目录下");
            } else {             / getRawFileDescriptor运行成功
                let fd = value.fd;
                console.log("LXG fd:" + fd)
                console.log("LXG fileDir:" + filesDir)
                / 打开文件流
                let inputStream = fs.fdopenStreamSync(fd, "r+");
                console.log("LXG inputStream:")
                let outputStream = fs.createStreamSync(openDocPath, "w+");
                / 以流的形式读取源文件内容并写入目标文件
                let bufSize = value.length;
                let readSize = 0;
                let buf = new ArrayBuffer(bufSize);

                class Option {
                    public offset: number = 0;
                    public length: number = bufSize;
                }

                let option = new Option();
                option.offset = value.offset;
                let readLen = await inputStream.read(buf, option);
                readSize += readLen;
                while (readLen > 0) {
                    await outputStream.write(buf);
                    option.offset = readSize + value.offset;
                    option.length = value.length - readSize >= bufSize ? bufSize : value.length - readSize;
                    readLen = await inputStream.read(buf, option);
                    readSize += readLen;
                }
                / 关闭文件流
                inputStream.closeSync();
                outputStream.closeSync();
            }
            return cb(error);
        });

    } catch (error) {
        console.log("[rawfile_copy_to_sandbox] 未能成功将rawfile目录下的 sample.pdf文件拷贝到应用沙盒目录下");
        return cb(error);
    }
    return cb(new Error());
}


// 使用UI Extensions组件构建一个功能丰富的PDF阅读器
@Entry
@Component
struct
Index
{
@State
    message
:
    string = 'Open Doc';
@State
    clickID
:
    number = 0;
@State
    uiextensionsManager
:
    UIExtensionsManager = new UIExtensionsManager(getContext(this));
public
    openDoc()
    {
        this.uiextensionsManager.openDocument(openDocPath, '', (error: number) => {
            if (error == FoxitRDKNative.common.e_ErrSuccess) {
                this.clickID = 1;
            }
        })
    }

    build()
    {
        Row()
        {
            Column()
            {
                if (this.clickID == 0) {
                    Button(this.message)
                        .fontSize(50)
                        .fontWeight(FontWeight.Bold)
                        .onClick(() => {
                            let res = fs.accessSync(openDocPath);
                            if (!res) {
                                copy_rawfile__to_sandbox((error) => {
                                    if (error == null) {
                                        try {
                                            this.openDoc()
                                        } catch (err) {
                                            console.info(` fail callback, code: ${err.code}, msg: ${err.msg}`)
                                        }
                                    }
                                })
                            } else {
                                this.openDoc()
                            }
                        })
                } else {
                    UIExtensionsComponent({uiExtMgr: this.uiextensionsManager})
                        .width('100%')
                        .height('100%')
                        .backgroundColor(Color.Black)
                }
            }
        .
            width('100%')
        }
    .
        height('100%')
    }
}

如果您使用华为模拟器来运行该示例工程。运行成功后,点击 打开文档 按钮,即可看到 Sample.pdf 文档显示如下图所示 。至此,您已经成功构建了一个功能齐全的 PDF 阅读器。 功能支持详情,请参阅 福昕 PDF SDK 移动版功能对照表。

open_doc_2_result.png