Android Java实现PDF文本搜索高亮(免收费库)及Spire.Doc报错求助
解决方案:Android Java实现PDF文本搜索与高亮(无收费第三方库)
一、先解决现有代码的报错问题
你当前使用的spire.doc是Word文档处理库,并非PDF处理库,而且它依赖Android系统不存在的java.awt.print.Printable等桌面Java类,直接导致编译报错。必须替换为适合Android的开源免费PDF处理库,推荐Apache PDFBox(Apache旗下开源项目,完全免费)。
二、实现步骤与代码
按照你要求的不修改原文件,单独保存注释信息的思路,分为两个核心部分:文本搜索并保存位置信息、渲染PDF时叠加绘制高亮区域。
1. 添加PDFBox依赖
在build.gradle(Module level)中添加依赖:
implementation 'org.apache.pdfbox:pdfbox-android:2.0.32' // 可选:处理特殊字体时添加 implementation 'org.apache.pdfbox:fontbox:2.0.32'
2. 核心实现逻辑
(1)搜索文本并保存高亮注释
创建工具类负责搜索文本,将高亮位置信息保存到本地:
import android.content.Context; import android.content.SharedPreferences; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.TextPosition; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class PdfSearchHelper { private static final String PREFS_NAME = "PdfHighlights"; private static final String HIGHLIGHT_KEY = "highlights_"; // 保存高亮信息的实体类 public static class HighlightAnnotation { public int pageNum; public float x; public float y; public float width; public float height; public String searchText; public HighlightAnnotation(int pageNum, float x, float y, float width, float height, String searchText) { this.pageNum = pageNum; this.x = x; this.y = y; this.width = width; this.height = height; this.searchText = searchText; } } // 搜索文本并保存高亮信息 public static List<HighlightAnnotation> searchAndSaveHighlights(Context context, String pdfPath, String searchText) throws IOException { List<HighlightAnnotation> highlights = new ArrayList<>(); PDDocument document = PDDocument.load(new File(pdfPath)); for (int pageIndex = 0; pageIndex < document.getNumberOfPages(); pageIndex++) { PDPage page = document.getPage(pageIndex); PDFTextStripper stripper = new PDFTextStripper() { @Override protected void writeString(String text, List<TextPosition> textPositions) throws IOException { if (text.contains(searchText)) { for (TextPosition tp : textPositions) { if (tp.getUnicode().contains(searchText)) { // 转换PDF坐标系到Android坐标系(PDF原点在左下角,Android在左上角) float pageHeight = page.getMediaBox().getHeight(); float x = tp.getX(); float y = pageHeight - tp.getY() - tp.getHeight(); float width = tp.getWidth(); float height = tp.getHeight(); highlights.add(new HighlightAnnotation(pageIndex, x, y, width, height, searchText)); } } } super.writeString(text, textPositions); } }; stripper.setStartPage(pageIndex + 1); stripper.setEndPage(pageIndex + 1); stripper.getText(document); } document.close(); // 保存到SharedPreferences(也可换用JSON文件或数据库) saveHighlights(context, pdfPath, highlights); return highlights; } // 保存高亮信息到本地 private static void saveHighlights(Context context, String pdfPath, List<HighlightAnnotation> highlights) { SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); Gson gson = new Gson(); String json = gson.toJson(highlights); editor.putString(HIGHLIGHT_KEY + pdfPath.hashCode(), json); editor.apply(); } // 获取已保存的高亮信息 public static List<HighlightAnnotation> getSavedHighlights(Context context, String pdfPath) { SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); String json = prefs.getString(HIGHLIGHT_KEY + pdfPath.hashCode(), ""); if (json.isEmpty()) { return new ArrayList<>(); } Gson gson = new Gson(); return gson.fromJson(json, new TypeToken<List<HighlightAnnotation>>(){}.getType()); } }
(2)PDF渲染时叠加高亮
自定义View,使用Android自带PdfRenderer渲染PDF,并在页面上绘制高亮区域:
import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.os.ParcelFileDescriptor; import android.view.View; import androidx.annotation.NonNull; import java.io.File; import java.io.IOException; import android.graphics.pdf.PdfRenderer; public class PdfHighlightView extends View { private PdfRenderer mPdfRenderer; private PdfRenderer.Page mCurrentPage; private List<PdfSearchHelper.HighlightAnnotation> mHighlights; private Paint mHighlightPaint; public PdfHighlightView(Context context) { super(context); init(); } private void init() { mHighlightPaint = new Paint(); mHighlightPaint.setColor(0x80FFEB3B); // 半透明黄色高亮 mHighlightPaint.setStyle(Paint.Style.FILL); } // 加载PDF文件 public void loadPdf(File pdfFile) throws IOException { if (mPdfRenderer != null) { mPdfRenderer.close(); } ParcelFileDescriptor fd = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY); mPdfRenderer = new PdfRenderer(fd); mHighlights = PdfSearchHelper.getSavedHighlights(getContext(), pdfFile.getAbsolutePath()); invalidate(); } // 跳转到指定页码 public void goToPage(int pageIndex) { if (mPdfRenderer != null && pageIndex >= 0 && pageIndex < mPdfRenderer.getPageCount()) { if (mCurrentPage != null) { mCurrentPage.close(); } mCurrentPage = mPdfRenderer.openPage(pageIndex); invalidate(); } } @Override protected void onDraw(@NonNull Canvas canvas) { super.onDraw(canvas); if (mCurrentPage == null) { return; } // 渲染PDF页面 float scale = Math.min((float) getWidth() / mCurrentPage.getWidth(), (float) getHeight() / mCurrentPage.getHeight()); canvas.save(); canvas.scale(scale, scale); mCurrentPage.render(canvas, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); canvas.restore(); // 绘制高亮区域 if (mHighlights != null && !mHighlights.isEmpty()) { canvas.save(); canvas.scale(scale, scale); for (PdfSearchHelper.HighlightAnnotation annotation : mHighlights) { if (annotation.pageNum == mCurrentPage.getIndex()) { RectF rect = new RectF(annotation.x, annotation.y, annotation.x + annotation.width, annotation.y + annotation.height); canvas.drawRect(rect, mHighlightPaint); } } canvas.restore(); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); if (mCurrentPage != null) { mCurrentPage.close(); } if (mPdfRenderer != null) { mPdfRenderer.close(); } } }
3. 使用示例
在Activity中调用搜索与显示:
import android.os.Bundle; import android.os.Environment; import androidx.appcompat.app.AppCompatActivity; import java.io.File; import java.io.IOException; public class PdfActivity extends AppCompatActivity { private PdfHighlightView mPdfView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPdfView = new PdfHighlightView(this); setContentView(mPdfView); // 示例:搜索PDF中的"Android"文本 File pdfFile = new File(Environment.getExternalStorageDirectory(), "sample.pdf"); try { // 搜索并保存高亮信息(建议放在子线程执行) PdfSearchHelper.searchAndSaveHighlights(this, pdfFile.getAbsolutePath(), "Android"); // 加载PDF并显示第一页 mPdfView.loadPdf(pdfFile); mPdfView.goToPage(0); } catch (IOException e) { e.printStackTrace(); } } }
三、关键说明
- 坐标系转换:PDF与Android坐标系原点位置不同,需通过页面高度计算转换Y坐标,保证高亮位置准确。
- 性能优化:大文件搜索时需放在子线程执行,避免阻塞主线程;可添加分页搜索逻辑提升效率。
- 权限处理:Android 10及以上需申请文件访问权限,或通过MediaStore读取PDF文件。
- 注释存储:示例用
SharedPreferences存储,若需跨设备同步或存储大量注释,可换用Room数据库或JSON文件。
内容的提问来源于stack exchange,提问作者mohamadjamous
相关产品推荐
相关产品推荐

