itextpdf + 相邻行内容错乱
背景: 之前博客中提到抽取PDF中表格数据的时候,spire.pdf无法读取表格外的内容。正常情况使用itextpdf,可以按纯文本形式读取所有内容,但是没有意外的意外又出现了,那就是那些肉眼看起来在一行的内容,使用itextpdf读取后发现,部分字段值要么跑到上面行去了,要么跑到下面行去了,有的却是正常的,这种不确定的现象肯定要解决的,不然线上问题处理不完。(说了那么多可能想想象不出来,也不能泄露数据,大家后面遇到再说)
博客内容精选:
1、Servlet请求体重复读&修改新姿势
2、根据请求获取后端接口详情
3、封装Springboot项目的starter-sdk新方式
4、Springboot全局处理完整版
5、itextpdf读取文本时上下行位置错乱
6、JAVA读取PDF表格内容
7、JAVA读取PDF出现内容错乱
根因:字段位置坐标存在微小差距,行坐标如果不一致,则itextpdf认为不是一行,但是这个差距极小,肉眼无法分辨(鼠标双击后可看到此现象)
解决办法有多种,以下将简述两种情况:
1、转图片OCR
这种方法直接,但识别效果依靠OCR的识别精确度,如果采用这种方式,还不如用OCR的表格识别功能,一次性解决了。如果需要商用级别的稳定度和准确度,基本都要付费使用腾讯或阿里的商用OCR了。
2、溯源定位找适配
既然知道了根据,那我们就看itextpdf读取pdf里面的内容,以及它是如何把这些内容封装后完整的展现出来的。具体代码流程此处忽略,下面直接贴出解决代码:
/**
* 重写文本提取后的排序策略,其它逻辑保持不变
*/
private static final LocationTextExtractionStrategy.TextChunkLocationStrategy textChunkLocationStrategy = new LocationTextExtractionStrategy.TextChunkLocationStrategy() {
@Override
public LocationTextExtractionStrategy.TextChunkLocation createLocation(TextRenderInfo renderInfo, LineSegment baseline) {
return new LocationTextExtractionStrategy.TextChunkLocationDefaultImp(baseline.getStartPoint(), baseline.getEndPoint(), renderInfo.getSingleSpaceWidth()) {
@Override
public int compareTo(LocationTextExtractionStrategy.TextChunkLocation other) {
if (this == other) {
return 0;
} else {
int rslt = Integer.compare(this.orientationMagnitude(), other.orientationMagnitude());
if (rslt != 0) {
return rslt;
} else {
// 行内容键值对相对应的纵坐标可能不一致,此处重写排序逻辑,消除肉眼内的坐标差值
rslt = Integer.compare(this.distPerpendicular(), other.distPerpendicular());
return rslt == 0 || Math.abs(this.distPerpendicular() - other.distPerpendicular()) == 1 ? Float.compare(this.distParallelStart(), other.distParallelStart()) : rslt;
}
}
}
};
}
};
处理入口:
import com.itextpdf.text.pdf.parser.LineSegment;
import com.itextpdf.text.pdf.parser.LocationTextExtractionStrategy;
import com.itextpdf.text.pdf.parser.PdfTextExtractor;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
PdfReader pdfReader = new PdfReader("path/file");
for (int page = 1; page <= pdfReader.getNumberOfPages(); page++) {
// 此处使用优化后的排序策略,即可解决坐标差异问题
String text = PdfTextExtractor.getTextFromPage(pdfReader, page, new LocationTextExtractionStrategy(textChunkLocationStrategy));
System.out.println(text);
}
总结:当初遇到这个问题也是一脸懵,itext也只是仅仅用过而已,没有完整了解其底层的原理。为了解决这个必须解决的问题,迫不得已静下心来逐步溯源分析,慢慢调试优化,在猜测与验证的过程中发现底层开放了扩展接口,进而在不影响效率及流程的情况下解决了此问题,在此记录供同行伙伴共勉。