当前位置: 首页 > news >正文

PDF注释的加载和保存的实现

PDF注释功能文档

概述

本文档详细说明了PDF注释功能的实现,包括注释的加载和保存功能。该功能基于Android PDFBox库实现,支持Ink类型注释的读取和写入。

功能模块

1. 注释加载功能 (getAnnotation())

功能描述

从PDF文件中加载已存在的注释,并将其显示在PDFView上。

实现流程
private fun getAnnotation() {// 1. 加载PDF文档val document = loadPdfFromAssets(this, SAMPLE_FILE) ?: return// 2. 处理加密PDFif (document.isEncrypted) {try {val policy = StandardProtectionPolicy("", "", AccessPermission())document.protect(policy)Log.i(TAG, "getAnnotation: --PDF解密成功")} catch (e: Exception) {Log.i(TAG, "getAnnotation: --解密失败: ${e.message}")document.close()return}}// 3. 创建线程安全的注释列表val lineGraphicsList = CopyOnWriteArrayList<LineGraphic>()// 4. 异步加载注释lifecycleScope.launch {val lineGraphics = PdfAnnotationLoader.loadAnnotationsFromPdf(context = this@MainActivity,document,)lineGraphicsList.addAll(lineGraphics)// 5. 更新UI显示if (lineGraphicsList.isNotEmpty()) {mBinding.pdfView.lineGraphics = lineGraphicsListmBinding.pdfView.redraw()}}
}
关键特性
  • 加密PDF支持: 自动处理加密PDF的解密
  • 异步加载: 使用协程避免阻塞主线程
  • 线程安全: 使用CopyOnWriteArrayList确保线程安全
  • UI更新: 加载完成后自动重绘PDF视图

2. 注释保存功能 (pickSave())

功能描述

将用户在PDFView上绘制的注释保存到PDF文件中,支持Ink类型注释的写入。

实现流程
private fun pickSave() {try {// 1. 加载PDF文档val document = loadPdfFromAssets(this, SAMPLE_FILE) ?: returnval lineGraphicsList = mBinding.pdfView.lineGraphicsrunBlocking {// 2. 计算页面高度映射val heightMap = HashMap<Int, Float>()val count = document.pages.countvar previousHeight = 0ffor (pageIndex in 0 until count) {val page = document.getPage(pageIndex)val curPageHeight = page.mediaBox.heightpreviousHeight += curPageHeightheightMap[pageIndex] = previousHeight}// 3. 处理每个注释for (lineGraphic in lineGraphicsList) {if (lineGraphic.pageIndex < 0) continuewithContext(Dispatchers.IO) {// 4. 坐标转换val inkPaths = mutableListOf<FloatArray>()val floatList = mutableListOf<Float>()val pageIndex = lineGraphic.pageIndexval page = document.getPage(pageIndex)val absolutPoints = lineGraphic.relativePoints// 5. 坐标系统转换val pdfWidth = page.mediaBox.widthval pdfHeight = page.mediaBox.heightfor (point in absolutPoints) {val screenX = point.xval screenY = point.y// 转换为PDF坐标系统val pdfX = screenX * pdfWidthval pdfY = (1f - screenY) * pdfHeightfloatList.add(pdfX)floatList.add(pdfY)}inkPaths.add(floatList.toFloatArray())// 6. 创建Ink注释val inkAnnotation = PDAnnotationInk()inkAnnotation.subtype = "Ink"// 7. 计算边界矩形val bounds = calculateInkBounds(inkPaths, page.mediaBox)inkAnnotation.rectangle = bounds// 8. 创建外观流val normalAppearance = PDAppearanceStream(document)normalAppearance.bBox = boundsPDPageContentStream(document, normalAppearance).use { cs ->cs.setStrokingColor(AWTColor.RED)cs.setLineWidth(2f)// 绘制轨迹for (path in inkPaths) {cs.moveTo(path[0], path[1])for (index in 2 until path.size step 2) {cs.lineTo(path[index], path[index + 1])}cs.stroke()}}// 9. 设置外观字典val apDict = COSDictionary()apDict.setItem(COSName.N, normalAppearance)inkAnnotation.cosObject.setItem(COSName.AP, apDict)// 10. 设置注释属性inkAnnotation.isPrinted = trueinkAnnotation.isNoZoom = falseinkAnnotation.isNoRotate = false// 11. 添加到页面page.annotations.add(inkAnnotation)}}}// 12. 保存文件val file = File(this.getExternalFilesDir(null), "shapes_example.pdf")if (file.exists()) {file.delete()}file.createNewFile()savePdfAsync(document, file) { result ->if (result.success) {Log.i(TAG, "保存成功")} else {Log.i(TAG, "保存失败: ${result.message}")}}} catch (e: Exception) {Log.i(TAG, "加载失败:${e.message}")}
}
关键特性
  • 坐标转换: 将屏幕坐标转换为PDF坐标系统
  • 多页面支持: 支持跨页面的注释处理
  • 异步处理: 使用协程处理IO操作
  • 外观流: 创建PDF标准的外观流确保兼容性
  • 文件保存: 异步保存到本地文件系统

辅助功能

1. 边界计算 (calculateInkBounds())

private fun calculateInkBounds(inkPaths: MutableList<FloatArray>,pageSize: PDRectangle
): PDRectangle {var minX = Float.MAX_VALUEvar minY = Float.MAX_VALUEvar maxX = Float.MIN_VALUEvar maxY = Float.MIN_VALUEinkPaths.forEach { path ->for (i in path.indices step 2) {minX = minOf(minX, path[i])minY = minOf(minY, path[i + 1])maxX = maxOf(maxX, path[i])maxY = maxOf(maxY, path[i + 1])}}// 添加10像素边距return PDRectangle((minX - 10).coerceAtLeast(0f),(minY - 10).coerceAtLeast(0f),(maxX - minX + 20).coerceAtMost(pageSize.width),(maxY - minY + 20).coerceAtMost(pageSize.height))
}

2. 异步保存 (savePdfAsync())

private fun savePdfAsync(document: PDDocument,outputFile: File,callback: (SaveResult) -> Unit
) {CoroutineScope(Dispatchers.IO).launch {val result = try {document.save(outputFile)SaveResult(true, "保存成功")} catch (e: Exception) {SaveResult(false, "保存失败: ${e.message}")} finally {document.close()}withContext(Dispatchers.Main) {callback(result)}}
}

注释类型支持

Ink注释

  • 类型: 自由绘图注释
  • 格式: PDF标准Ink注释
  • 兼容性: 支持WPS等主流PDF阅读器
  • 属性: 颜色、线宽、边界矩形等

坐标系统

坐标转换流程

  1. 屏幕坐标: 用户在PDFView上的触摸点
  2. 相对坐标: 转换为0-1范围的相对坐标
  3. PDF坐标: 转换为PDF文档的绝对坐标
  4. Y轴反转: PDF坐标系Y轴向下,需要反转

转换公式

// 屏幕坐标转PDF坐标
val pdfX = screenX * pdfWidth
val pdfY = (1f - screenY) * pdfHeight

错误处理

依赖库

核心依赖

  • com.tom_roush:pdfbox-android: PDF处理核心库
  • com.github.barteksc:android-pdf-viewer: PDF显示组件
  • org.jetbrains.kotlinx:kotlinx-coroutines: 协程支持
http://www.dtcms.com/a/318225.html

相关文章:

  • Enhancing Long Video Question Answering with Scene-Localized Frame Grouping
  • python中的推导式
  • Android PDFBox 的使用指南
  • 力扣热题100------136.只出现一次的数字
  • 【纵火犯的春天】纵火犯是如何解题leetcode的?
  • Python驱动的无人机多光谱-点云融合技术在生态三维建模与碳储量/生物量/LULC估算中的全流程实战
  • JDK9+ Method.class.getDeclaredFields() Method实例将不能再直接通过反射修改
  • 无人机航拍数据集|第4期 无人机太阳光伏板红外目标检测YOLO数据集10945张yolov11/yolov8/yolov5可训练
  • 大疆无人机使用eport连接Jetson主板实现目标检测
  • selenium操作指南
  • 前端路由守卫
  • JavaWeb服务器/servlet容器(Tomcat、Undertow 、WebLogic)
  • 前端应用场景题目(待总结优化)
  • 攻防世界WEB(新手模式)20-unseping
  • 基于 kubeadm 搭建 k8s 集群
  • 京东商品评论接口开发全指南:从数据获取到分析应用实战
  • 【20205CVPR-目标检测方向】基于事件的高效目标检测:具有空间和时间注意力的混合神经网络
  • Lodash 的终极进化Radashi
  • JAVA 程序员cursor 和idea 结合编程
  • 北京JAVA基础面试30天打卡03
  • SAP MR51 显示不是ALV格式的问题
  • 开源远程工具rustdesk
  • 力扣 hot100 Day67
  • Linux firewall 防火墙管理
  • SpringBoot 接入SSE实现消息实时推送的优点,原理以及实现
  • React:生命周期
  • 二、Istio流量治理(一)
  • 佳文鉴赏 || FD-LLM:用于机器故障诊断的大规模语言模型
  • 性能为王:一次从压测到调优的实战全流程复盘
  • PHP常用日期时间函数