Android PDFBox 使用指南
概述
PDFBox是一个强大的PDF处理库,在Android平台上也有对应的实现。本指南将介绍如何在Android项目中使用PDFBox进行PDF文件的加载、读取、修改等操作。
依赖配置
在 app/build.gradle 中添加PDFBox依赖:
dependencies {
implementation 'com.tom-roush:pdfbox-android:2.0.27.0'
}
核心功能
1. 初始化PDFBox
在使用PDFBox之前,必须先初始化资源加载器:
// 在Application或Activity的onCreate中调用
PDFBoxResourceLoader.init(context)
2. 加载PDF文件
从Assets文件夹加载
fun loadPdfFromAssets(context: Context, fileName: String): PDDocument? {
return try {
context.assets.open(fileName).use { inputStream ->
PDDocument.load(inputStream, MemoryUsageSetting.setupMixed(1000 * 1024 * 1024))
}
} catch (e: IOException) {
null
}
}
从文件路径加载
fun loadPdfFromFile(filePath: String): PDDocument? {
return try {
PDDocument.load(File(filePath), MemoryUsageSetting.setupMixed(1000 * 1024 * 1024))
} catch (e: IOException) {
null
}
}
3. 获取PDF信息
fun getPdfInfo(document: PDDocument): String {
val info = StringBuilder()
// 获取页面数量
val pageCount = document.numberOfPages
info.append("页面数量: $pageCount\n")
// 获取文档信息
val documentInformation = document.documentInformation
if (documentInformation != null) {
info.append("标题: ${documentInformation.title ?: "无"}\n")
info.append("作者: ${documentInformation.author ?: "无"}\n")
info.append("主题: ${documentInformation.subject ?: "无"}\n")
info.append("创建者: ${documentInformation.creator ?: "无"}\n")
info.append("创建日期: ${documentInformation.creationDate ?: "无"}\n")
info.append("修改日期: ${documentInformation.modificationDate ?: "无"}\n")
}
return info.toString()
}
4. 提取文本内容
4.1. 提取整个文档的文本
fun extractText(document: PDDocument): String {
return try {
val stripper = PDFTextStripper()
stripper.text = document
} catch (e: IOException) {
"提取文本失败"
}
}
4.2. 提取指定页面的文本
fun extractTextFromPage(document: PDDocument, pageIndex: Int): String {
return try {
val stripper = PDFTextStripper()
stripper.startPage = pageIndex + 1
stripper.endPage = pageIndex + 1
stripper.text = document
} catch (e: IOException) {
"提取页面文本失败"
}
}
5. 获取页面信息
fun getPageInfo(document: PDDocument, pageIndex: Int): String {
return try {
val page = document.getPage(pageIndex)
val mediaBox = page.mediaBox
val cropBox = page.cropBox
"页面 ${pageIndex + 1}:\n" +
"媒体框 - 宽度: ${mediaBox.width}, 高度: ${mediaBox.height}\n" +
"裁剪框 - 宽度: ${cropBox.width}, 高度: ${cropBox.height}\n" +
"旋转角度: ${page.rotation}°\n" +
"注释数量: ${page.annotations.size}"
} catch (e: Exception) {
"获取页面信息失败"
}
}
6. 添加注释
6.1. 添加文本注释
fun addTextAnnotation(document: PDDocument, pageIndex: Int, x: Float, y: Float, text: String) {
try {
val page = document.getPage(pageIndex)
val annotation = PDAnnotationInk()
annotation.subtype = "FreeText"
// 设置注释位置和大小
val rect = PDRectangle(x, y, x + 100, y + 50)
annotation.rectangle = rect
// 设置注释内容
annotation.contents = text
// 设置颜色
annotation.color = AWTColor.YELLOW
// 添加到页面
page.annotations.add(annotation)
} catch (e: Exception) {
Log.e(TAG, "添加文本注释失败: ${e.message}")
}
}
6.1. 添加手绘注释
fun addInkAnnotation(document: PDDocument, pageIndex: Int, points: List<FloatArray>) {
try {
val page = document.getPage(pageIndex)
// 创建手绘注释
val inkAnnotation = PDAnnotationInk()
inkAnnotation.subtype = "Ink"
// 计算边界
val bounds = calculateInkBounds(points, page.mediaBox)
inkAnnotation.rectangle = bounds
// 创建外观流
val normalAppearance = PDAppearanceStream(document)
normalAppearance.bBox = bounds
// 绘制轨迹
PDPageContentStream(document, normalAppearance).use { cs ->
cs.setStrokingColor(AWTColor.RED)
cs.setLineWidth(2f)
for (path in points) {
if (path.size >= 4) {
cs.moveTo(path[0], path[1])
for (index in 2 until path.size step 2) {
cs.lineTo(path[index], path[index + 1])
}
cs.stroke()
}
}
}
// 设置外观
val apDict = COSDictionary()
apDict.setItem(COSName.N, normalAppearance)
inkAnnotation.cosObject.setItem(COSName.AP, apDict)
// 添加到页面
page.annotations.add(inkAnnotation)
} catch (e: Exception) {
Log.e(TAG, "添加手绘注释失败: ${e.message}")
}
}
7. 保存PDF文件
fun savePdf(document: PDDocument, outputPath: String): Boolean {
return try {
document.save(outputPath)
true
} catch (e: IOException) {
false
}
}
8. 关闭文档
fun closeDocument(document: PDDocument) {
try {
document.close()
} catch (e: IOException) {
Log.e(TAG, "关闭PDF文档失败: ${e.message}")
}
}
使用示例
完整处理流程示例
fun processPdfExample(context: Context, fileName: String) {
// 1. 加载PDF
val document = loadPdfFromAssets(context, fileName)
if (document == null) {
Log.e(TAG, "无法加载PDF文件")
return
}
try {
// 2. 获取PDF信息
val info = getPdfInfo(document)
Log.i(TAG, "PDF信息:\n$info")
// 3. 提取文本
val text = extractText(document)
Log.i(TAG, "PDF文本内容:\n$text")
// 4. 获取第一页信息
if (document.numberOfPages > 0) {
val pageInfo = getPageInfo(document, 0)
Log.i(TAG, "第一页信息:\n$pageInfo")
// 5. 添加文本注释
addTextAnnotation(document, 0, 100f, 100f, "这是一个测试注释")
// 6. 添加手绘注释示例
val samplePoints = listOf(
floatArrayOf(50f, 50f, 100f, 100f, 150f, 50f),
floatArrayOf(200f, 200f, 250f, 250f, 300f, 200f)
)
addInkAnnotation(document, 0, samplePoints)
}
// 7. 保存修改后的PDF
val outputPath = context.getExternalFilesDir(null)?.absolutePath + "/modified_$fileName"
if (savePdf(document, outputPath)) {
Log.i(TAG, "PDF处理完成,已保存到: $outputPath")
}
} finally {
// 8. 关闭文档
closeDocument(document)
}
}
注意事项
-
内存管理: PDFBox需要大量内存,建议使用
MemoryUsageSetting.setupMixed()来优化内存使用。 -
异常处理: 所有PDF操作都应该包含适当的异常处理。
-
资源释放: 使用完PDF文档后,务必调用
close()方法释放资源。 -
线程安全: PDFBox操作应该在后台线程中执行,避免阻塞UI线程。
-
文件权限: 确保应用有适当的文件读写权限。