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.pswp.cn/pingmian/92573.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/92573.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/92573.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Linux環境下實現簡單TCP通信(c)

具體代碼實現 server.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>#define PORT 8080 #define BUFFER_SIZE 1024void handle_client(int client_s…

炫酷圓形按鈕調色器

<!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>圓形按鈕顏色控制器</title><style>bod…

Vue 3 的編譯時優化如何改寫 DOM 操作規則

在現代前端開發中&#xff0c;框架級優化正悄然改變我們處理性能瓶頸的方式。與手動優化策略不同&#xff0c;Vue 3 的編譯器在構建階段就完成了關鍵性能改造&#xff0c;為 DOM 操作效率帶來質的飛躍。一、虛擬DOM的隱藏成本虛擬DOM&#xff08;Virtual DOM&#xff09;通過內…

Angular初學者入門第二課——.ts、.d.ts、.state.ts的區別(精品)

初次接觸 Angular 實際項目時&#xff0c;發現里邊有很多不同后綴的文件&#xff0c;雖然沒深入研究過&#xff0c;但根據其他編程語言的經驗猜測這應該是通過后綴名來區分文件的作用。后來有時間研究了一下具體的細節和不同點&#xff0c;就有了今天這篇文章&#xff0c;這些知…

進程狀態+進程優先級+進程上下文切換解讀

一、進程狀態 什么是進程狀態&#xff1f;進程狀態指的是在操作系統中進程在生命周期中所處的不同階段。進程狀態有哪些呢&#xff1f;我們可以看到上述圖片 進程狀態分為&#xff1a;創建狀態、就緒狀態、運行狀態、阻塞狀態和終止狀態所有的操作系統在實現進程狀態變化的時候…

Android 原生與 Flutter 通信完整實現 (Kotlin 版)

1. 項目配置 pubspec.yaml 添加依賴 dependencies:flutter:sdk: flutterprovider: ^6.0.52. Flutter 端實現 狀態管理類 // settings_provider.dart import package:flutter/foundation.dart;class SettingsProvider with ChangeNotifier {String _themeColor blue;bool _dark…

數字圖像處理3

圖像線性濾波——目的就是濾去噪聲&#xff0c;但是邊緣會模糊&#xff0c;整體也模糊線性&#xff1a;鄰域平均法&#xff08;4鄰域平均和8鄰域平均&#xff09;用當前運算點所在鄰域的平均值來代替該點的平均值im_for_read"D:\AAAproject\PYproject\EXPERuse\zaosheng.j…

Linux發行版分類與Centos替代品

讓centos7氣的不輕&#xff0c;這玩意兒太老了&#xff0c;什么都不好配置。 目錄Linux 發行版的大致分類1. Red Hat 系列&#xff08;RPM 系&#xff09;2. Debian 系列&#xff08;DEB 系&#xff09;3. Arch 系列4. SUSE 系列CentOS 7 的替代品推薦AlmaLinux 和 Rocky Linux…

大語言模型提示工程與應用:大語言模型對抗性提示安全防御指南

對抗性提示工程 學習目標 理解大語言模型中對抗性提示的風險與防御機制&#xff0c;掌握提示注入、提示泄露和越獄攻擊的檢測方法&#xff0c;培養安全防護意識。 相關知識點 對抗性攻擊類型防御技術 學習內容 1 對抗性攻擊類型 1.1 提示注入 提示注入旨在通過使用巧妙…

避不開的數據拷貝(2)

接著上周未完的話題 避不開的數據拷貝。 既然處理器是通用機器&#xff0c;就沒有專屬數據&#xff0c;所以數據都要從別處調來&#xff0c;這就涉及到了數據搬運&#xff0c;就有了外設的概念。由于不同外設和處理器一起共享數據存儲&#xff0c;時間會花在兩方面&#xff1a…

娃哈哈經銷商“大洗牌”:砍掉年銷300萬以下經銷商

文 | 大力財經據第一財經報道&#xff0c;娃哈哈在宗馥莉“鐵腕”策略推動下&#xff0c;正經歷經銷商體系的重大變革&#xff0c;陸續砍掉年銷低于300萬元的經銷商&#xff0c;方式有時頗為激進&#xff0c;“一刀切”的做法引發諸多爭議&#xff0c;部分經銷商反饋存在款項未…

drippingblues靶機通關練習筆記

前言 將靶機導入到vmware虛擬機上 靶機下載地址&#xff1a;https://download.vulnhub.com/drippingblues/drippingblues.ova 將網段都設置為nat 信息收集 ip端口掃描 netdiscover -r 192.168.25.1/24 --確定ip nmap -A -p- 192.168.25. kalid的ip&#xff1a;1…

QT第三講- 機制、宏、類庫模塊

文章目錄 ?? 一、Qt核心機制與類庫 ?? 1. 元對象系統(Meta-Object System) ? 2. 信號與槽(Signals & Slots) ? 通信機制 ?? 3. 屬性系統(Property System) 動態屬性 例程 類的附加信息 Q_CLASSINFO 例程 ?? 二、全局定義與容器 ?? 1. 全局數據類型與函數…

(LeetCode 每日一題) 869. 重新排序得到 2 的冪 (哈希表+枚舉)

題目&#xff1a;869. 重新排序得到 2 的冪 思路&#xff1a;哈希表枚舉。先預處理出所有的2的冪數&#xff0c;用哈希表來存儲。 C版本&#xff1a; class Solution { public:// 哈希表存儲所有 2的冪數 按升序排列的形式unordered_set<string> st;// 預處理出所有的2…

WebAssembly技術詳解:從瀏覽器到云原生的高性能革命

引言&#xff1a;WebAssembly的誕生與使命 2015年&#xff0c;當Mozilla、Google、Microsoft和Apple四大瀏覽器廠商聯合發布WebAssembly&#xff08;Wasm&#xff09;技術預覽時&#xff0c;業界尚未意識到這將開啟Web性能的新紀元。作為繼HTML、CSS、JavaScript之后的第四種We…

性能解析案例

異步io是內核fd與應用程序直接的關系io 多路復用1.檢測io是否就緒2.read/write消息隊列kafka&#xff1a;1.典型應用 &#xff1a;異步處理&#xff0c;系統解耦&#xff0c;流量削峰&#xff0c;日志處理2.核心原理&#xff1a;kafka體系結構以及讀寫流程3.具體操作&#xff1…

青龍峽拔韭菜

我們一年四季&#xff0c;除了冬天不往山里進&#xff0c;其余季節&#xff0c;只要天氣允許&#xff0c;我們都會進山。在山里拔韭菜&#xff0c;是我們百做不煩的一件事。今年大旱&#xff0c;從五月份上山找韭菜&#xff0c;沒有如愿。直到入秋后&#xff0c;我們再次去青龍…

5、docker鏡像管理命令

1、命令總覽命令&#xff08;含關鍵參數&#xff09;作用出現頻率備注docker buildx build --platform … -t … --push .一次構建并推送多平臺鏡像高頻需先 docker buildx create --usedocker buildx build -o typedocker,destxxx.tar .構建后離線導出 tar 包中頻只導出單平臺…

阿里云ECS云服務器臨時升級帶寬方法

阿里云ECS云服務器臨時升級帶寬方法一、背景與需求二、原理三、操作步驟步驟 0: 準備工作步驟 1: 創建彈性網卡 (ENI)步驟 2: 創建并綁定彈性公網IP (EIP)步驟 3: SSH登錄ECS并切換到高速通道 (eth1)步驟 4: 執行你的高帶寬任務步驟 5: 任務完成&#xff0c;切回默認網卡 (eth0…

Java語言簡介

一.Java語言的起源 Java語言的前身是Oka語言,是美國Sun Microsystems公司于1991年推出的,僅限于公司內部使用的語言。1995年,Sun公司將Oak語言更名為Java語言,并正式向公眾推出。這之后,Java語言不斷更新,其類庫越來越豐富,性能逐步提升,應用領域也顯著拓展,已成為當今…