You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

三、关键说明

  1. 坐标系转换:PDF与Android坐标系原点位置不同,需通过页面高度计算转换Y坐标,保证高亮位置准确。
  2. 性能优化:大文件搜索时需放在子线程执行,避免阻塞主线程;可添加分页搜索逻辑提升效率。
  3. 权限处理:Android 10及以上需申请文件访问权限,或通过MediaStore读取PDF文件。
  4. 注释存储:示例用SharedPreferences存储,若需跨设备同步或存储大量注释,可换用Room数据库或JSON文件。

内容的提问来源于stack exchange,提问作者mohamadjamous

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:24:50