如何在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 } }
关键改进点
- 阿拉伯语支持修复:移除手动反转文本的代码,依赖pdfbox-android内置的RTL处理逻辑,确保文本顺序正确
- 格式保留逻辑:
- 通过Y坐标差值判断段落间距,自动添加
<p>标签 - 通过字体大小阈值识别标题,生成
<h3>标签 - 识别常见项目符号,自动生成
<ul><li>列表结构
- 通过Y坐标差值判断段落间距,自动添加
- 图片提取:遍历PDF页面资源,提取图片转为Base64插入HTML,保留图文结构
- 逻辑简化:只重写
writeString(List<TextPosition>)方法,避免与processTextPosition的逻辑冲突
使用注意事项
- 字体大小、段落间距阈值可根据目标PDF的实际排版调整
- 大图片使用Base64会导致HTML体积过大,建议将图片保存到本地存储,再用文件路径作为
img标签的src - 复杂PDF(多列布局、特殊字体)可能需要额外处理坐标和字体信息
内容的提问来源于stack exchange,提问作者mohamadjamous
相关产品推荐
相关产品推荐

