PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件

0 阅读10分钟

PDF 预览与签名批注写回 支持安卓 iOS 鸿蒙 UTS插件

介绍

  1. 支持 AndroidiOSHarmonyOS 的 PDF 预览能力封装,可用于打开本地 PDF 文件、翻页、缩放、签名记录、批注记录以及将记录写回到新 PDF,支持手势缩放预览,支持预览加密PDF。
  2. 适用于 uni-appuni-app x App 平台,不支持 Web 和各类小程序平台。
  3. 核心流程为:通过 <cz-pdf-viewer> 组件挂载原生 PDF 视图,调用 open 加载文件,之后调用 prevPagenextPagegoToPagezoomTo 等方法进行交互;签名/批注通过 addSignatureRecordaddAnnotationRecord 等记录 API 管理,最后通过 applyRecordsToPdfAsync 将记录合入新 PDF。
  4. 即使当前平台不支持原生 PDF 预览(如模拟器),签名与批注记录模型仍可独立使用。

平台支持

平台支持情况
Android支持
iOS支持
HarmonyOS支持
Web不支持
小程序不支持

组件属性

属性名称类型默认值说明
srcstring""PDF 文件路径
sourceType"filePath" | "contentUri" | "asset""filePath"文件来源类型;iOS 当前以 filePath/asset 为主;Harmony 以沙箱 filePath 为主
passwordstringPDF 加密密码
defaultPagenumber1打开后默认展示的页码
enableSwipebooleantrue是否允许滑动手势翻页(仅 Android)
swipeHorizontalbooleanfalse是否横向翻页(仅 Android)
enableDoubleTapbooleantrue是否启用双击缩放(仅 Android)
pageSnapbooleanfalse是否吸附翻页(仅 Android)
pageFlingbooleanfalse是否 fling 翻页(仅 Android)
nightModebooleanfalse夜间模式(仅 Android)
autoSpacingbooleanfalse自动页间距(Android 支持;iOS 近似映射为 page break 显示;Harmony 当前未单独接入)
pageSpacingnumber0页间距,具体效果以平台原生实现为准
fitPolicy"width" | "height" | "both""width"页面适配策略;iOS 以 sizeToFit 基准缩放近似映射;Harmony 为原生 pageFit 映射,语义非完全一致
minZoomnumber1最小缩放比例
midZoomnumber1.75中间缩放比例
maxZoomnumber3最大缩放比例
enableAnnotationRenderingbooleantrue是否渲染 PDF 内嵌批注(仅 Android 支持开关)
autoOpenbooleanfalse组件挂载后是否自动根据 src 调用 open

组件事件

事件名称说明
loadPDF 加载完成,可通过 event.detail.state 获取当前阅读状态
pageChange页码变化
zoomChange缩放比例变化
error发生错误,可通过 event.detail.state.lastErrorCodelastErrorMessage 获取错误信息
viewerTap点击 PDF 阅读区域(仅 Android)
recordsChange签名或批注记录发生变化(Android、HarmonyOS 支持;iOS 当前通过组件内部同步)

组件方法(defineExpose)

方法名称参数返回说明
openCzPdfOpenOptions | nullboolean打开 PDF 文件。为 null 时使用组件属性构建打开参数
reloadboolean重新加载当前 PDF
closevoid关闭当前 PDF,释放原生资源
prevPageboolean | null(animated)boolean上一页
nextPageboolean | null(animated)boolean下一页
goToPagenumber(page), boolean | null(animated)boolean跳转到指定页码
zoomTonumber(scale)boolean设置缩放比例
resetZoomboolean重置缩放
fitToWidthnumber | null(page)boolean适配页面宽度,不传则适配当前页
getViewStateCzPdfViewState获取当前阅读状态快照
getRecordsCzPdfRecordBundle获取当前签名与批注记录
setRecordsCzPdfRecordBundlevoid替换全部记录
clearRecordsvoid清空全部记录
addSignatureRecordCzPdfSignatureRecordvoid添加一条签名记录
removeSignatureRecordstring(id)boolean按 ID 移除一条签名记录
addAnnotationRecordCzPdfAnnotationRecordvoid添加一条批注记录
removeAnnotationRecordstring(id)boolean按 ID 移除一条批注记录

独立函数

函数名称参数返回说明
applyRecordsToPdfCzPdfApplyRecordsOptionsCzPdfApplyRecordsResult以同步方式将签名/批注记录写入新 PDF
applyRecordsToPdfAsyncCzPdfApplyRecordsOptions, CzPdfApplyRecordsCallbackvoid以异步方式将签名/批注记录写入新 PDF(推荐用于生产环境)
createCzPdfRecordStoreCzPdfRecordBundle | nullICzPdfRecordStore创建一个独立于预览组件的记录管理实例,适用于无原生 PDF 预览的场景

平台差异说明

不同平台底层 PDF 能力不同,部分功能在三端的支持范围并不完全一致,接入前建议先阅读本节。

APIAndroidiOSHarmonyOS说明
open支持支持支持三端都支持打开本地 PDF 文件;iOS/Harmony 的 asset 方案建议业务层先将资源转为绝对路径
reload支持支持支持关闭后再调用 reload 将返回 false,不会自动恢复
close支持支持支持关闭后会释放原生资源
prevPage / nextPage支持支持支持三端都支持翻页
goToPage支持支持支持三端都支持指定页码跳转
zoomTo支持支持支持iOS 按 sizeToFit 基准归一化映射;Harmony 使用原生 PDFKit zoom 映射
resetZoom支持支持支持三端都支持重置缩放至基准倍率
fitToWidth支持支持支持iOS 缩放基于 PDFView bounds 计算;Harmony 依赖原生布局就绪状态
签名记录模型支持支持支持三端纯数据模型一致,签名图片路径建议使用绝对路径
批注记录模型支持支持支持三端纯数据模型一致,支持 highlight、underline、square、text 四种批注类型
applyRecordsToPdf支持支持支持写回 PDF 为独立能力,不依赖组件实例;写回时会先清除输入 PDF 已有批注再写入新记录
横向翻页支持支持不支持Harmony 当前未接入横向分页布局
夜间模式支持不支持不支持仅 Android 支持
pageSnap / pageFling支持不支持不支持仅 Android 支持
双击缩放开关支持不支持不支持仅 Android 支持控制开关;iOS/Harmony 由原生组件决定行为
enableAnnotationRendering支持不支持不支持仅 Android 支持控制是否渲染 PDF 内嵌批注
recordsChange 事件支持不支持支持iOS 当前通过组件内部 getRecords 同步记录状态

iOS 特别说明

  1. iOS 使用 PDFKit 作为底层渲染引擎,缩放范围基于 sizeToFit 基准归一化映射。
  2. fitToWidth 依赖 PDFView 布局就绪,如果调用时布局未完成可能返回失败。
  3. createCzPdfRecordStore 创建的记录管理实例在所有平台上完全独立于原生 PDF 视图,可用于模拟器或无原生预览的纯数据处理场景。
  4. 写回 PDF 时会在后台 IO 队列加载 PDF 文档、清除已有批注、写入新记录并保存,最终在主线程回调结果。同步版本 applyRecordsToPdf 会阻塞当前线程,生产环境建议使用 applyRecordsToPdfAsync

HarmonyOS 特别说明

  1. HarmonyOS 原生 PDFView 在 close 后会隐藏视图(display:none),避免遮挡后续内容。
  2. open 后会自动恢复视图可见性。
  3. HarmonyOS 写回 PDF 时默认使用 originalInputPath(原始文件路径),而非当前查看的 PDF 路径,以规避重复写回叠加批注的问题。
  4. swipeHorizontal 横向翻页能力鸿蒙侧当前未支持。

Android 特别说明

  1. Android 使用 PDFBox 引擎进行 PDF 写回,打开 PDF 时会在 IO 线程预处理预览文件。
  2. Android 支持 contentUri 方式打开 PDF(如从系统文件选择器获取的 URI)。
  3. viewerTap 事件仅在 Android 平台触发。
  4. enableSwipeenableDoubleTappageSnappageFlingnightMode 等阅读器专属参数仅 Android 平台生效。

数据结构说明

CzPdfViewState

getViewState() 或 load/pageChange/zoomChange 事件的 detail.state 中包含:

字段类型说明
isSupportedboolean当前平台是否支持原生 PDF 预览
isReadyboolean原生视图是否就绪
isLoadedbooleanPDF 是否已加载
pagenumber当前页码
pageCountnumber总页数
zoomnumber当前缩放比例
minZoomnumber最小缩放比例
midZoomnumber中间缩放比例
maxZoomnumber最大缩放比例
sourcestring | null当前打开的文件路径
sourceTypestring | null当前文件来源类型
lastErrorCodestring | null最近一次错误码
lastErrorMessagestring | null最近一次错误描述

CzPdfSignatureRecord

签名记录结构:

type CzPdfSignatureRecord = {
  id: string           // 唯一标识
  page: number         // 所在页码
  rect: CzPdfRect      // 签名区域
  imagePath: string    // 签名图片路径(建议使用绝对路径)
  createdAt?: number   // 创建时间戳(毫秒)
  extra?: UTSJSONObject // 扩展数据
}

CzPdfAnnotationRecord

批注记录结构:

type CzPdfAnnotationRecord = {
  id: string                    // 唯一标识
  type: "highlight" | "underline" | "square" | "text"  // 批注类型
  page: number                  // 所在页码
  rects: CzPdfRect[]            // 批注区域数组
  color?: string                // 颜色,如 "#FFEB3B"
  opacity?: number              // 透明度 0-1
  contents?: string             // 批注文本内容
  author?: string               // 作者
  iconName?: string             // 图标名称
  createdAt?: number            // 创建时间戳(毫秒)
  extra?: UTSJSONObject         // 扩展数据
}

CzPdfRect

矩形区域结构:

type CzPdfRect = {
  x: number      // 左上角 x
  y: number      // 左上角 y
  width: number  // 宽度
  height: number // 高度
  unit?: "pageRatio" | "pdfPoint"  // 坐标单位,默认使用页面比例(0-1)
}

CzPdfRecordBundle

记录集合:

type CzPdfRecordBundle = {
  signatures: CzPdfSignatureRecord[]
  annotations: CzPdfAnnotationRecord[]
}

CzPdfApplyRecordsResult

写回结果:

字段类型说明
successboolean是否写回成功
outputPathstring | null输出文件路径
writtenSignatureCountnumber成功写入的签名数量
writtenAnnotationCountnumber成功写入的批注数量
errorCodestring | null失败时的错误码
errorMessagestring | null失败时的错误描述

快速开始

1. 引入组件

在插件市场中导入cz-pdf-viewer到uni_modules,直接在 uvue 模板中使用 <cz-pdf-viewer>

2. 基础 PDF 预览

// 模板中
<cz-pdf-viewer
  ref="viewerRef"
  src="/path/to/document.pdf"
  sourceType="filePath"
  autoOpen
  @load="onLoad"
  @pageChange="onPageChange"
  @error="onError"
/>

// 脚本中
const viewerRef = ref()

function onLoad(event : UniNativeViewEvent) : void {
  const state = event.detail.state
  console.log(`PDF 加载完成,共 ${state.pageCount} 页`)
}

function onPageChange(event : UniNativeViewEvent) : void {
  const state = event.detail.state
  console.log(`当前页:${state.page}/${state.pageCount}`)
}

3. 通过方法控制

const viewer = viewerRef.value
if (viewer != null) {
  // 打开 PDF
  viewer.open({ src: "/path/to/document.pdf" })

  // 翻页
  viewer.nextPage()
  viewer.prevPage()

  // 跳转到第 3 页
  viewer.goToPage(3)

  // 缩放
  viewer.zoomTo(1.5)
  viewer.resetZoom()
  viewer.fitToWidth()
}

4. 添加签名与批注记录

const viewer = viewerRef.value
if (viewer == null) return

// 添加签名记录
viewer.addSignatureRecord({
  id: "sig-001",
  page: 1,
  rect: { x: 0.1, y: 0.1, width: 0.3, height: 0.15 },
  imagePath: "/path/to/signature.png"
} as CzPdfSignatureRecord)

// 添加高亮批注
viewer.addAnnotationRecord({
  id: "ann-001",
  type: "highlight",
  page: 1,
  rects: [{ x: 0.15, y: 0.3, width: 0.7, height: 0.05 }],
  color: "#FFEB3B",
  opacity: 0.5,
  contents: "重点内容"
} as CzPdfAnnotationRecord)

// 添加矩形框批注
viewer.addAnnotationRecord({
  id: "ann-002",
  type: "square",
  page: 1,
  rects: [{ x: 0.2, y: 0.5, width: 0.6, height: 0.2 }],
  color: "#FF5722",
  opacity: 0.8
} as CzPdfAnnotationRecord)

5. 写回到新 PDF

import { applyRecordsToPdfAsync, CzPdfApplyRecordsResult } from "@/uni_modules/cz-pdf-viewer"

const viewer = viewerRef.value
const records = viewer.getRecords()

applyRecordsToPdfAsync({
  inputPath: "/path/to/original.pdf",
  outputPath: "/path/to/output.pdf",
  records: records,
  overwrite: true
}, (result : CzPdfApplyRecordsResult) => {
  if (result.success) {
    console.log(`写回成功:签名 ${result.writtenSignatureCount} 条,批注 ${result.writtenAnnotationCount} 条`)
    console.log(`输出文件:${result.outputPath}`)
  } else {
    console.log(`写回失败:${result.errorMessage}`)
  }
})

6. 脱离预览使用记录模型

import { createCzPdfRecordStore, applyRecordsToPdfAsync } from "@/uni_modules/cz-pdf-viewer"

// 创建独立的记录管理实例(无需原生 PDF 视图)
const store = createCzPdfRecordStore(null)

store.addAnnotationRecord({
  id: "ann-x",
  type: "highlight",
  page: 1,
  rects: [{ x: 0.1, y: 0.2, width: 0.8, height: 0.06 }],
  color: "#FFEB3B"
} as CzPdfAnnotationRecord)

applyRecordsToPdfAsync({
  inputPath: "/path/to/input.pdf",
  outputPath: "/path/to/output.pdf",
  records: store.getRecords(),
  overwrite: true
}, (result) => {
  console.log(`写入 ${result.writtenAnnotationCount} 条批注`)
})

常用方法说明

open

打开一个 PDF 文件。参数为 CzPdfOpenOptions,其中 src 为必填项。

重要参数:

参数类型默认值说明
srcstring无(必填)PDF 文件路径
sourceTypeCzPdfSourceType"filePath"文件来源类型
passwordstring加密 PDF 的密码
defaultPagenumber1打开后默认展示的页码
swipeHorizontalbooleanfalse横向翻页(Android、iOS 支持)
fitPolicy"width" | "height" | "both""width"页面适配策略
minZoom / midZoom / maxZoomnumber1 / 1.75 / 3缩放范围

prevPage / nextPage / goToPage

翻页相关方法。prevPage 到文件开头停止,nextPage 到末尾停止,均返回 boolean 表示是否成功。goToPage 直接跳转到指定页。

zoomTo / resetZoom / fitToWidth

缩放相关方法。zoomTo 接受缩放比例(相对基准倍率),resetZoom 恢复至 1 倍,fitToWidth 将指定页缩放至与视图等宽。

getRecords / setRecords / clearRecords

记录管理方法。getRecords 返回当前全部记录的深拷贝,setRecords 用于整体替换,clearRecords 清空全部签名与批注。

调用 clearRecords 后立即进行写回不会将旧记录带入输出 PDF。

addSignatureRecord / removeSignatureRecord

签名记录的增删。addSignatureRecord 需要提供 idpagerectimagePath,签名图片建议使用绝对路径以避免路径解析问题。

addAnnotationRecord / removeAnnotationRecord

批注记录的增删。支持四种批注类型:

  • highlight:高亮,可指定多个矩形区域
  • underline:下划线
  • square:矩形框,取第一个矩形区域
  • text:文字批注

applyRecordsToPdf / applyRecordsToPdfAsync

将签名与批注记录写入新的 PDF 文件。执行时会:

  1. 加载输入 PDF
  2. 清除输入 PDF 上已有的批注(避免叠加)
  3. 逐页写入签名图片与批注标记
  4. 保存到输出路径

推荐使用异步版本 applyRecordsToPdfAsync,避免阻塞 UI。

完整示例

import {
  applyRecordsToPdfAsync,
  CzPdfAnnotationRecord,
  CzPdfApplyRecordsResult,
  CzPdfSignatureRecord
} from "@/uni_modules/cz-pdf-viewer"

export default {
  methods: {
    async demoFlow() {
      const viewer = this.$refs.viewerRef

      // 打开 PDF
      viewer.open({
        src: "/var/mobile/Containers/Data/Application/.../doc.pdf",
        sourceType: "filePath"
      })

      // 添加签名
      viewer.addSignatureRecord({
        id: `sig-${Date.now()}`,
        page: 1,
        rect: { x: 0.7, y: 0.8, width: 0.25, height: 0.12 },
        imagePath: "/path/to/signature.png",
        createdAt: Date.now()
      } as CzPdfSignatureRecord)

      // 添加高亮
      viewer.addAnnotationRecord({
        id: `ann-${Date.now()}`,
        type: "highlight",
        page: 1,
        rects: [{ x: 0.1, y: 0.15, width: 0.8, height: 0.04 }],
        color: "#FFEB3B",
        opacity: 0.4,
        createdAt: Date.now()
      } as CzPdfAnnotationRecord)

      // 写回到新 PDF
      const records = viewer.getRecords()
      applyRecordsToPdfAsync({
        inputPath: "/var/mobile/Containers/Data/Application/.../doc.pdf",
        outputPath: "/var/mobile/Containers/Data/Application/.../output.pdf",
        records: records,
        overwrite: true
      }, (result : CzPdfApplyRecordsResult) => {
        if (result.success) {
          uni.showToast({ title: "PDF 已保存" })
        } else {
          uni.showToast({ title: `保存失败:${result.errorMessage}`, icon: "none" })
        }
      })
    }
  }
}

注意事项

  1. 仅支持手机真机调试,不支持模拟器上的原生 PDF 预览(记录模型仍可独立使用)。
  2. 调用 open 前确保 src 指向的文件存在且可读。
  3. 调用 close 后再执行 reload 将返回 false,需要重新调用 open 打开文件。
  4. 签名图片路径建议使用绝对路径,避免写回时因相对路径解析失败导致签名丢失。
  5. 写回 PDF 时会先清除输入 PDF 上已有的批注标记,避免多次写回叠加。
  6. applyRecordsToPdf 是同步方法,大文件或大量批注场景建议使用 applyRecordsToPdfAsync
  7. 不同平台、不同文件格式(加密、损坏等)的支持程度不同,业务接入前建议通过 loaderror 事件进行流程控制。