Android PDFBox 库使用指南

1,269 阅读2分钟

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)
    }
}

注意事项

  1. 内存管理: PDFBox需要大量内存,建议使用MemoryUsageSetting.setupMixed()来优化内存使用。

  2. 异常处理: 所有PDF操作都应该包含适当的异常处理。

  3. 资源释放: 使用完PDF文档后,务必调用close()方法释放资源。

  4. 线程安全: PDFBox操作应该在后台线程中执行,避免阻塞UI线程。

  5. 文件权限: 确保应用有适当的文件读写权限。

更多资源