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

如何在Android应用中提取带格式的PDF文本并转为HTML?

Android PDF转HTML(保留格式+支持阿拉伯语)

问题分析

你的现有代码存在几个关键问题:

  • writeString和processTextPosition同时被重写,导致文本重复输出、逻辑混乱
  • 手动反转阿拉伯语文本是错误的——pdfbox-android的TextPosition.getUnicode()已经处理了RTL语言的字符顺序,手动反转会导致文本错乱
  • 没有利用TextPosition提供的字体大小、坐标、间距等信息识别标题、段落、列表等结构

修复后的实现代码

核心转换器类

import com.tom_roush.pdfbox.pdmodel.PDDocument;
import com.tom_roush.pdfbox.pdmodel.PDPage;
import com.tom_roush.pdfbox.pdmodel.PDResources;
import com.tom_roush.pdfbox.pdmodel.graphics.PDXObject;
import com.tom_roush.pdfbox.pdmodel.graphics.form.PDFormXObject;
import com.tom_roush.pdfbox.pdmodel.graphics.image.PDImageXObject;
import com.tom_roush.pdfbox.text.PDFTextStripper;
import com.tom_roush.pdfbox.text.TextPosition;
import java.io.File;
import java.io.IOException;
import java.util.List;

public class PDFToHTMLConverter extends PDFTextStripper {
    private final StringBuilder html;
    private float previousY = -1;
    private float currentFontSize = -1;
    // 可根据PDF实际情况调整阈值
    private static final float PARAGRAPH_SPACING_THRESHOLD = 10;
    private static final float HEADING_FONT_THRESHOLD = 14;

    public PDFToHTMLConverter() throws IOException {
        super();
        this.html = new StringBuilder();
        // 启用按位置排序文本,适配RTL语言
        setSortByPosition(true);
    }

    @Override
    protected void startDocument(PDDocument document) {
        html.append("<html><body style='font-family: sans-serif;'>");
        // 提前提取PDF中的图片
        extractImages(document);
    }

    @Override
    protected void endDocument(PDDocument document) {
        html.append("</body></html>");
    }

    @Override
    protected void writeString(String text, List<TextPosition> textPositions) throws IOException {
        if (text.trim().isEmpty()) {
            return;
        }

        TextPosition firstPos = textPositions.get(0);
        float currentY = firstPos.getYDirAdj();
        currentFontSize = firstPos.getFontSize();

        // 判断是否需要结束上一段落
        if (previousY != -1 && Math.abs(currentY - previousY) > PARAGRAPH_SPACING_THRESHOLD) {
            html.append("</p>");
        }

        // 识别标题(通过字体大小阈值)
        if (currentFontSize > HEADING_FONT_THRESHOLD) {
            html.append(String.format("<h3 style='font-size: %.1fpx; margin: 0.8em 0 0.3em;'>", currentFontSize));
            html.append(text);
            html.append("</h3>");
        } else {
            // 识别项目符号列表
            if (text.startsWith("•") || text.startsWith("●") || text.startsWith("-")) {
                html.append("<ul><li style='margin: 0.2em 0;'>").append(text.substring(1).trim()).append("</li></ul>");
            } else {
                // 普通段落处理
                if (previousY == -1 || Math.abs(currentY - previousY) > PARAGRAPH_SPACING_THRESHOLD) {
                    html.append("<p style='margin: 0.5em 0; line-height: 1.5;'>");
                }
                html.append(text);
            }
        }

        previousY = currentY;
    }

    // 提取PDF中的图片并插入HTML
    private void extractImages(PDDocument document) {
        int imageIndex = 0;
        for (PDPage page : document.getPages()) {
            PDResources resources = page.getResources();
            for (String name : resources.getXObjectNames()) {
                PDXObject xObject = resources.getXObject(name);
                if (xObject instanceof PDImageXObject) {
                    PDImageXObject image = (PDImageXObject) xObject;
                    try {
                        byte[] imageBytes = image.getByteArray();
                        String base64 = android.util.Base64.encodeToString(imageBytes, android.util.Base64.DEFAULT);
                        html.append(String.format("<img src='data:image/png;base64,%s' alt='PDF图片%d' style='max-width: 100%%; margin: 1em 0;'/>", base64, ++imageIndex));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else if (xObject instanceof PDFormXObject) {
                    // 处理嵌套表单中的图片
                    processFormXObject((PDFormXObject) xObject);
                }
            }
        }
    }

    private void processFormXObject(PDFormXObject form) {
        PDResources resources = form.getResources();
        for (String name : resources.getXObjectNames()) {
            PDXObject xObject = resources.getXObject(name);
            if (xObject instanceof PDImageXObject) {
                PDImageXObject image = (PDImageXObject) xObject;
                try {
                    byte[] imageBytes = image.getByteArray();
                    String base64 = android.util.Base64.encodeToString(imageBytes, android.util.Base64.DEFAULT);
                    html.append(String.format("<img src='data:image/png;base64,%s' alt='表单图片' style='max-width: 100%%; margin: 1em 0;'/>", base64));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public String getHTMLText(File pdfFile) throws IOException {
        try (PDDocument document = PDDocument.load(pdfFile)) {
            this.writeText(document, new NullWriter());
        }
        return html.toString();
    }
}

NullWriter类(保持不变)

import java.io.Writer;
import java.io.IOException;

public class NullWriter extends Writer {
    @Override
    public void write(char[] cbuf, int off, int len) throws IOException {
        // Do nothing
    }

    @Override
    public void flush() throws IOException {
        // Do nothing
    }

    @Override
    public void close() throws IOException {
        // Do nothing
    }
}

关键改进点

  1. 阿拉伯语支持修复:移除手动反转文本的代码,依赖pdfbox-android内置的RTL处理逻辑,确保文本顺序正确
  2. 格式保留逻辑:
    • 通过Y坐标差值判断段落间距,自动添加<p>标签
    • 通过字体大小阈值识别标题,生成<h3>标签
    • 识别常见项目符号,自动生成<ul><li>列表结构
  3. 图片提取:遍历PDF页面资源,提取图片转为Base64插入HTML,保留图文结构
  4. 逻辑简化:只重写writeString(List<TextPosition>)方法,避免与processTextPosition的逻辑冲突

使用注意事项

  • 字体大小、段落间距阈值可根据目标PDF的实际排版调整
  • 大图片使用Base64会导致HTML体积过大,建议将图片保存到本地存储,再用文件路径作为img标签的src
  • 复杂PDF(多列布局、特殊字体)可能需要额外处理坐标和字体信息

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 20:40:23